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; }