题解 [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;
}
posted @ 2021-02-17 09:16  ezlmr  阅读(46)  评论(0编辑  收藏  举报