Manthan, Codefest 19 (open for everyone, rated, Div. 1 + Div. 2)-E. Let Them Slide
题意:n×w的方格中,每一行有cnti个数字,
每一行的数字都连续的放在一起,但是可以任意的平移。问每一列的最大和为多少。
单调栈+差分
能想到维护每个数字在该行能影响到哪些列,一开始想单调栈,但是不知道维护出来之后怎么累加的问题,
对于每行的第i+1列,其实和第i列有很大的关系,(由第i列加一些值减一些值得到)//差不多滑窗???
记录每个值影响区间的左右端点之后,左端点处就把该值加进来,右端点加一处就把该值影响减去,
最后对差分数组做一个前缀和求出原数组。
方案一:
对于每一行的第i个数字,记录左边第一个大于它的数字下标j,
右边第一个大于等于它的数字下标k.这个数字可以贡献的区间范围受到了j和k的限制.
例如,如果某一个区域j可以移动到这,i也可以移动到这,那么肯定优先选j. 所以相当于i可以影响的范围缩小.
方案二虽然操作骚气但是空间和时间复杂度都很挫,,相比之下单调栈就优秀的多,其中的差分思想很重要
重点在i列与i+1列的关系:i+1列由i列加减一些值得到,所以维护一个sum[i+1]-sum[i]的数列,最后加和
int q,w,n;
int a[MAXN];
int l[MAXN],r[MAXN];
ll sum[MAXN];
void input()
{
rd(q),rd(w);
while(q--)
{
rd(n);
for(int i=1;i<=n;++i) rd(a[i]);
a[0]=a[n+1]=INF;//
for(int i=1;i<=n;++i)
{
l[i]=i;
while(a[l[i]-1]<a[i]) l[i]=l[l[i]-1];
}
for(int i=n;i>=1;--i)
{
r[i]=i;
while(a[r[i]+1]<a[i]) r[i]=r[r[i]+1];
}
for(int i=1;i<=n;++i)
{
int ll=i,rr=i+w-n;
if(l[i]!=1&&a[l[i]-1]>a[i]) ll=max(ll,w-n+l[i]);//如果左侧存在比他大的并且可以通过向右滑动到达这里,那么l<i
if(r[i]!=n) rr=min(rr,r[i]);//因为一开始就是按照在滑块在最左侧来算的,右侧比他大的不可能到达这里,所以限制条件只有滑动范围
if(a[i]<0) rr=min(n,rr),ll=max(ll,w-n+1);//如果小于零,还要看零能不能到达这里
if(rr<ll) continue;//对答案无贡献
sum[ll]+=a[i];
sum[rr+1]-=a[i];//差分
}
}
}
int main()
{
input();
for(int i=1;i<=w;++i)
{
sum[i]+=sum[i-1];
printf("%lld ",sum[i]);
}
cout<<endll;
//stop;
return 0;
} 大概是模拟做法
对于第 i 行的第 j 个方块,如果这排方块的总长度为cnt,那么可以算出来该方块可以移动到[[j,w+j−cnt]列,
对于每一列(假设为 j )我们可以使用两个vector分别记录第j列添加的元素(行,值),开始移除的元素(行,值)。
那么我们就可以动态更新第 j 列每行的待选元素。
对每一行开一个multiset,然后按列从左枚举到右,multiset[i]维护第i行在当前列中有哪些数字是可以放的,那么我们取最大的那个就行。
自己的STL太菜了补一下📑
1,multiset,元素可重复有序集合,插入与删除都是logn(然鹅vector是n),不指定规则默认从小到大
2,rbegin(),返回end()前一个迭代器,也就是最后一个元素值
3,erase(it),删除某元素或者某段,it为迭代器
int n,w;
ll ans[MAXN];
vector<pii >in[MAXN],out[MAXN];//维护每一列出进的值
multiset<ll>mx[MAXN];//对每一行维护最大值
void input()
{
rd(n),rd(w);
for(int i=1;i<=n;++i)
{
int len,l,r,d;rd(len);
if(len<w)
{
l=1,r=w-len,d=0;
in[l].push_back(make_pair(d,i));
out[r].push_back(make_pair(d,i));
l=len+1,r=w;
in[l].push_back(make_pair(d,i));
out[r].push_back(make_pair(d,i));
}
for(int j=1;j<=len;++j)
{
rd(d);l=j,r=j+w-len;
in[l].push_back(make_pair(d,i));//在该位置进,第i行
out[r].push_back(make_pair(d,i));//在该位置出
}
}
}
void solve()
{
for(int i=1;i<=w;++i)//按行处理
{
for(int j=0;j<in[i].size();++j)
{
pii d=in[i][j];//要进入的元素//用second找到更新哪一行
if(mx[d.second].begin()!=mx[d.second].end())
ans[i]-=*mx[d.second].rbegin();//rbegin指向最后一个值
mx[d.second].insert(d.first);
ans[i]+=*mx[d.second].rbegin();
}
ans[i+1]=ans[i];//每个out的后一个位置才是数字无法达到的地方
for(int j=0;j<out[i].size();++j)
{
pii d=out[i][j];//要删去的元素
ans[i+1]-=*mx[d.second].rbegin();
mx[d.second].erase(mx[d.second].find(d.first));
if(mx[d.second].begin()!=mx[d.second].end())
ans[i+1]+=*mx[d.second].rbegin();//
}
}
}
int main()
{
input();
solve();
for(int i=1;i<=w;++i) printf("%lld ",ans[i]);
cout<<endll;
return 0;
} 
京公网安备 11010502036488号