题解 [NOI2005] 维护数列
无旋treap解法
可以发现这道题是无旋treap的模板题,然而细节超多
数组解释:
- \(size\) 子树大小
- \(sum\) 子树权值和
- \(mxsum\) 最大子段和
- \(mxsuml\) 最大前缀和
- \(mxsumr\) 最大后缀和
- \(mx\) 最大值
- \(update\) 覆盖标记
- \(change\) 翻转标记
分裂合并
对于无旋treap维护数列的题,很自然的想到要使用排名分裂,剩下的部分就是套路了
void split(int p,int &a,int &b,int k) {
if(p==0) {
a=b=0;
return;
}
pushdown(p);//记得下传标记
if(k<=size[son[p][0]]) {
b=p;
split(son[p][0],a,son[b][0],k);
} else {
a=p;
split(son[p][1],son[a][1],b,k-size[son[p][0]]-1);
}
pushup(p);//记得上传标记
}
int merge(int a,int b) {
if(a==0||b==0) {
return a|b;
}
pushdown(a);
pushdown(b);
if(rd[a]<rd[b]) {
son[a][1]=merge(son[a][1],b);
pushup(a);
return a;
} else {
son[b][0]=merge(a,son[b][0]);
pushup(b);
return b;
}
}
上传
维护 \(size[p]\) 与 \(sum[p]\) 十分简单,就不赘述了
难点在于如何维护 \(mxsum[p]\)
定义对于节点 \(p\),左右儿子为 \(son[p][0/1]\),最大子段和为 \(mxsum[p]\),
我们发现,对于点 \(p\) 来说,通过维护最大前缀和 \(mxsuml[p]\) 和最大后缀和 \(mxsumr[p]\)
可以得到 \(mxsum[p]=max(mxsumr[son[p][0]]+v[p]+mxsuml[son[p][1]],max(mxsum[son[p][0]],mxsum[son[p][1]]))\)
接下来,我们还要考虑如何维护 \(mxsuml[p]\) 和 \(mxsumr[p]\)
以 \(mxsuml[p]\) 为例,显然有取与不取根节点两种选择,又因为右儿子的最大前缀和显然为正数,如果取了根节点就一定会取它
因此 \(mxsuml[p]=max(mxsuml[son[p][0]],sum[son[p][0]]+v[p]+mxsuml[son[p][1]])\)
同理 \(mxsumr[p]=max(mxsumr[son[p][1]],sum[son[p][1]]+v[p]+mxsumr[son[p][0]])\)
void pushup(int p) {
if(p==0)return;
size[p]=size[son[p][0]]+size[son[p][1]]+1;
sum[p]=sum[son[p][0]]+sum[son[p][1]]+v[p];
mx[p]=max(v[p],max(mx[son[p][0]],mx[son[p][1]]));
mxsum[p]=max(mxsumr[son[p][0]]+v[p]+mxsuml[son[p][1]],max(mxsum[son[p][0]],mxsum[son[p][1]]));
mxsuml[p]=max(mxsuml[son[p][0]],sum[son[p][0]]+v[p]+mxsuml[son[p][1]]);
mxsumr[p]=max(mxsumr[son[p][1]],sum[son[p][1]]+v[p]+mxsumr[son[p][0]]);
}
下传
将 \(cover\) 和 \(change\) 标记下传给儿子节点的同时,更新儿子节点即可
需要注意的是,在更新儿子节点的值时,也需要将其他信息一并更新,否则在上传时可能出错
还有,在翻转时,最大前缀和与最大后缀和需要交换
void cover(int p,int val) {
v[p]=val;
mx[p]=val;
sum[p]=val*size[p];
mxsum[p]=max(0,sum[p]);
mxsuml[p]=max(0,sum[p]);
mxsumr[p]=max(0,sum[p]);
update[p]=val;
}
void reverse(int p) {
swap(mxsuml[p],mxsumr[p]);
swap(son[p][0],son[p][1]);
change[p]^=1;
}
void pushdown(int p) {
if(update[p]!=inf) {
if(son[p][0])cover(son[p][0],v[p]);
if(son[p][1])cover(son[p][1],v[p]);
update[p]=inf;
}
if(change[p]) {
if(son[p][0])reverse(son[p][0]);
if(son[p][1])reverse(son[p][1]);
change[p]=0;
}
}
插入
我们将整棵树按照 \(pos\) 分为 \(x\) 和 \(y\) 两部分
当然,我们没有必要读入整个待插入序列后再将其建树,
在 \(pos\) 处断开后,我们可以直接将新节点合并到 \(x\) 上,最后再将两部分合并就好了
cin>>pos>>tot;
split(root,x,y,pos);
while(tot--) {
cin>>val;
x=merge(x,add(val));
}
root=merge(x,y);
删除
将整棵树在 \(pos-1\) 和 \(pos+tot-1\) 的位置分裂为 \(x\)、\(y\)、\(z\)
也就是说将待删除的序列 \(y\) 提出来,然后直接将 \(x\) 和 \(z\) 合并即可
cin>>pos>>tot;
split(root,x,y,pos-1);
split(y,y,z,tot);//此时的y已经与前pos-1个数分裂,因此再在tot处分裂即可
pushin(y);
root=merge(x,z);
覆盖
与删除相似,将待操作区间提出来,再打上覆盖标记即可
cin>>pos>>tot>>val;
split(root,x,y,pos-1);
split(y,y,z,tot);
cover(y,val);
root=merge(x,merge(y,z));
翻转
同理,提出区间后打上翻转标记即可
cin>>pos>>tot;
split(root,x,y,pos-1);
split(y,y,z,tot);
reverse(y);
root=merge(x,merge(y,z));
求和
同理,提出区间后输出sum即可
cin>>pos>>tot;
split(root,x,y,pos-1);
split(y,y,z,tot);
cout<<sum[y]<<endl;
root=merge(x,merge(y,z));
最大子段和
输出根节点的最大子段和即可
然而,因为本题的最大子段和不允许为空,因此当 \(mx[root]<0\) 时,直接输出 \(mx[root]\)即可,否则输出 \(mxsum[root]\)
if(mx[root]<0)cout<<mx[root]<<endl;
else cout<<mxsum[root]<<endl;
节约空间
因为本题空间卡的很紧,因此我们要将被删除的节点编号回收,这样可以控制空间的大小
具体来说,就是在删除时回收被删除节点编号,将他们放入一个栈中,新建节点时再去除即可
回收被删除节点代码如下
stack<int>ns;
void pushin(int p) {
if(p==0)return;
ns.push(p);
pushin(son[p][0]);
pushin(son[p][1]);
}
新建节点代码如下
int add(int val) {
int p;
if(ns.empty()) {
p=++cnt;
} else {
p=ns.top();
ns.pop();
}
v[p]=val;
rd[p]=rand();
mx[p]=val;
size[p]=1;
sum[p]=v[p];
mxsum[p]=max(v[p],0);
mxsuml[p]=max(v[p],0);
mxsumr[p]=max(v[p],0);
son[p][0]=son[p][1]=0;
update[p]=inf;
change[p]=0;
return p;
}
代码
#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<stack>
#define inf 0x3f3f3f3f
using namespace std;
const int maxn=500005;
int n,m,cnt,root;
int v[maxn],rd[maxn],son[maxn][2];
int size[maxn],sum[maxn];
int mxsum[maxn],mxsuml[maxn],mxsumr[maxn],mx[maxn]= {-inf};
int change[maxn],update[maxn];
void pushup(int p) {
if(p==0)return;
size[p]=size[son[p][0]]+size[son[p][1]]+1;
sum[p]=sum[son[p][0]]+sum[son[p][1]]+v[p];
mx[p]=max(v[p],max(mx[son[p][0]],mx[son[p][1]]));
mxsum[p]=max(mxsumr[son[p][0]]+v[p]+mxsuml[son[p][1]],max(mxsum[son[p][0]],mxsum[son[p][1]]));
mxsuml[p]=max(mxsuml[son[p][0]],sum[son[p][0]]+v[p]+mxsuml[son[p][1]]);
mxsumr[p]=max(mxsumr[son[p][1]],sum[son[p][1]]+v[p]+mxsumr[son[p][0]]);
}
void cover(int p,int val) {
v[p]=val;
mx[p]=val;
sum[p]=val*size[p];
mxsum[p]=max(0,sum[p]);
mxsuml[p]=max(0,sum[p]);
mxsumr[p]=max(0,sum[p]);
update[p]=val;
}
void reverse(int p) {
swap(mxsuml[p],mxsumr[p]);
swap(son[p][0],son[p][1]);
change[p]^=1;
}
void pushdown(int p) {
if(update[p]!=inf) {
if(son[p][0])cover(son[p][0],v[p]);
if(son[p][1])cover(son[p][1],v[p]);
update[p]=inf;
}
if(change[p]) {
if(son[p][0])reverse(son[p][0]);
if(son[p][1])reverse(son[p][1]);
change[p]=0;
}
}
stack<int>ns;
void pushin(int p) {
if(p==0)return;
ns.push(p);
pushin(son[p][0]);
pushin(son[p][1]);
}
int add(int val) {
int p;
if(ns.empty()) {
p=++cnt;
} else {
p=ns.top();
ns.pop();
}
v[p]=val;
rd[p]=rand();
mx[p]=val;
size[p]=1;
sum[p]=v[p];
mxsum[p]=max(v[p],0);
mxsuml[p]=max(v[p],0);
mxsumr[p]=max(v[p],0);
son[p][0]=son[p][1]=0;
update[p]=inf;
change[p]=0;
return p;
}
void split(int p,int &a,int &b,int k) {
if(p==0) {
a=b=0;
return;
}
pushdown(p);
if(k<=size[son[p][0]]) {
b=p;
split(son[p][0],a,son[b][0],k);
} else {
a=p;
split(son[p][1],son[a][1],b,k-size[son[p][0]]-1);
}
pushup(p);
}
int merge(int a,int b) {
if(a==0||b==0) {
return a|b;
}
pushdown(a);
pushdown(b);
if(rd[a]<rd[b]) {
son[a][1]=merge(son[a][1],b);
pushup(a);
return a;
} else {
son[b][0]=merge(a,son[b][0]);
pushup(b);
return b;
}
}
int main() {
freopen("sequence.in","r",stdin);
freopen("sequence.out","w",stdout);
cin>>n>>m;
for(int i=1; i<=n; i++) {
int val;
cin>>val;
root=merge(root,add(val));
}
while(m--) {
string op;
int pos,tot,val;
cin>>op;
int x,y,z;
if(op=="INSERT") {
cin>>pos>>tot;
split(root,x,y,pos);
while(tot--) {
cin>>val;
x=merge(x,add(val));
}
root=merge(x,y);
} else if(op=="DELETE") {
cin>>pos>>tot;
split(root,x,y,pos-1);
split(y,y,z,tot);
pushin(y);
root=merge(x,z);
} else if(op=="MAKE-SAME") {
cin>>pos>>tot>>val;
split(root,x,y,pos-1);
split(y,y,z,tot);
cover(y,val);
root=merge(x,merge(y,z));
} else if(op=="REVERSE") {
cin>>pos>>tot;
split(root,x,y,pos-1);
split(y,y,z,tot);
reverse(y);
root=merge(x,merge(y,z));
} else if(op=="GET-SUM") {
cin>>pos>>tot;
split(root,x,y,pos-1);
split(y,y,z,tot);
cout<<sum[y]<<endl;
root=merge(x,merge(y,z));
} else if(op=="MAX-SUM") {
if(mx[root]<0)cout<<mx[root]<<endl;
else cout<<mxsum[root]<<endl;
}
}
return 0;
}