刷题总结——维护数列(NOI2005 bzoj1500 splay)
题目:
题目背景
NOI2005 DAY1 T2
题目描述
请写一个程序,要求维护一个数列,支持以下 6 种操作:(请注意,格式栏中的下划线‘_’表示实际输入文件中的空格)
输入格式
第 1 行包含两个数 N 和 M ,N 表示初始时数列中数的个数,M 表示要进行的操作数目。
第 2 行包含 N 个数字,描述初始时的数列。
以下 M 行,每行一条命令,格式参见问题描述中的表格。
输出格式
对于输入数据中的 GET-SUM 和 MAX-SUM 操作,向输出文件依次打印结果,每个答案(数字)占一行。
样例数据 1
输入 [复制]
9 8
2 -6 3 5 1 -5 -3 6 3
GET-SUM 5 4
MAX-SUM
INSERT 8 3 -5 7 2
DELETE 12 1
MAKE-SAME 3 3 2
REVERSE 3 6
GET-SUM 5 4
MAX-SUM
输出
-1
10
1
10
备注
【样例说明】
初始时,我们拥有数列:
2 -6 3 5 1 -5 -3 6 3
执行操作 GET-SUM 5 4 ,表示求出数列中从第 5 个数开始连续 4 个数字之和,如下图中的灰色部分 1+(-5)+(-3)+6 = -1:
2 -6 3 5 1 -5 -3 6 3
执行操作 MAX-SUM ,表示要求求出当前数列中最大的一段和,即如下图所示,应为 3+5+1+(-5)+(-3)+6+3 = 10:
2 -6 3 5 1 -5 -3 6 3
执行操作 INSERT 8 3 -5 7 2,即在数列中第 8 个数字后插入 -5 7 2,如下所示的灰色部分:
2 -6 3 5 1 -5 -3 6 -5 7 2 3
执行操作 DELETE 12 1,表示删除第 12 个数字,即最后一个:
2 -6 3 5 1 -5 -3 6 -5 7 2
执行操作 MAKE-SAME 3 3 2 ,表示从第 3 个数开始的 3 个数字,即下图中的灰色部分,统一修改为 2 :
2 -6 3 5 1 -5 -3 6 -5 7 2
改为
2 -6 2 2 2 -5 -3 6 -5 7 2
执行操作 REVERSE 3 6,表示取出数列中从第 3 个数开始的连续 6 个数:
如上所示的灰色部分 2 2 2 -5 -3 6 ,翻转后得到 6 -3 -5 2 2 2 ,并放回原来位置:
2 -6 6 -3 -5 2 2 2 -5 7 2
最后执行 GET-SUM 5 4 和 MAX-SUM ,不难得到答案 1 和 10 。
【评分方法】
本题设有部分分,对于每一个测试点:
- 如果你的程序能在输出文件正确的位置上打印 GET-SUM 操作的答案,你可以得到该测试点 60% 的分数;
- 如果你的程序能在输出文件正确的位置上打印 MAX-SUM 操作的答案,你可以得到该测试点 40% 的分数;
- 以上两条的分数可以叠加,即如果你的程序正确输出所有 GET-SUM 和 MAX-SUM 操作的答案,你可以得到该测试点 100% 的分数。
请注意:如果你的程序只能正确处理某一种操作,请确定在输出文件正确的位置上打印结果,即必须为另一种操作留下对应的行,否则我们不保证可以正确评分。
【数据规模和约定】
你可以认为在任何时刻,数列中至少有 1 个数。
输入数据一定是正确的,即指定位置的数在数列中一定存在。
50% 的数据中,任何时刻数列中最多含有 30,000 个数;
100% 的数据中,任何时刻数列中最多含有 500,000 个数。
100% 的数据中,任何时刻数列中任何一个数字均在 [-1,000,1,000] 内 。
100% 的数据中,M≤20,000,插入的数字总数不超过 4,000,000 个,输入文件大小不超过 20 MBytes 。
题解:
splay大boss题····能打得来splay操作基本没问题了·····
维护lm,rm,mx,sum,size分别表示包含该节点子树代表的区间的左端点的最大序列和,包含该节点子树代表的区间右端点的最大序列和,totsum:此区间的最大序列和最大值,以及该节点子树的和的总和和该节点子树的大小
然后就是各种提取区间操作了,在此引用Zig_zag题解,%%%%
注:标准结构:区间右端点+1(R)为根,区间左端点-1(L)为根的左儿子,这样目标区间就是L的右儿子,这种形式以后都用"标准结构"代替。
插入操作:先把需要插入的序列建成一个小平衡树(递归),转出标准结构,插到L的右儿子上就行了。
删除操作:转出标准结构,把L的右儿子切下来就行了(注意因为要回收空间,所以还是把要切的子树遍历了一遍,把这颗树上的节点标号入栈)。
覆盖操作:转出标准结构,把L的右儿子打上覆盖标记cov(以后下传的时候把节点的值改为cov的值,sum变为cov*size,la=ra=ma变为cov和sum中较大的一个,因为有负数的情况)
翻转操作:转出标准结构,把L的右儿子打上翻转标记rev(以后下传的时候要交换左右儿子并且交换la和ra)
求和操作:转出标准结构,答案就是L的右儿子的sum
最大值操作:转出标准结构,答案就是L的右儿子的ma
区间操作的时候一定要明白一点,就是打标记的同时做修改,就是说当一个点带了标记的时候,它已经被修改过了。
代码:
#include<iostream> #include<cstdio> #include<cstdlib> #include<cmath> #include<ctime> #include<cstring> #include<string> #include<algorithm> #include<cctype> using namespace std; const int N=1000005; const int inf=0x3f3f3f3f; int ans,n,m,father[N],son[N][2],rev[N],sum[N],lm[N],rm[N],mx[N],cov[N],size[N],key[N],a[N],que[N],tail,tot,st,ed,root; char s[25]; inline int R() { char c;int f=0,i=1; for(c=getchar();(c<'0'||c>'9')&&c!='-';c=getchar()); if(c=='-') i=-1,c=getchar(); for(;c<='9'&&c>='0';c=getchar()) f=(f<<3)+(f<<1)+c-'0'; return f*i; } void update(int x) { if (!x) return; lm[x]=max(lm[son[x][0]],sum[son[x][0]]+key[x]+max(0,lm[son[x][1]])); rm[x]=max(rm[son[x][1]],sum[son[x][1]]+key[x]+max(0,rm[son[x][0]])); mx[x]=max(max(mx[son[x][0]],mx[son[x][1]]),key[x]+max(0,rm[son[x][0]])+max(0,lm[son[x][1]])); sum[x]=sum[son[x][0]]+sum[son[x][1]]+key[x]; size[x]=size[son[x][0]]+size[son[x][1]]+1; } inline void reverse(int now) { if(!now) return; swap(son[now][1],son[now][0]); swap(lm[now],rm[now]); rev[now]^=1; } inline void cover(int now,int v) { if(!now) return; sum[now]=size[now]*v;key[now]=cov[now]=v; lm[now]=rm[now]=mx[now]=max(sum[now],v); } void pushdown(int x) { if (!x) return; if (rev[x]) { reverse(son[x][0]); reverse(son[x][1]); rev[x]=0; } if (cov[x]!=-inf) { cover(son[x][0],cov[x]); cover(son[x][1],cov[x]); cov[x]=-inf; } } inline int get(int now) { return son[father[now]][1]==now; } inline void rotate(int now) { int fa=father[now],ofa=father[fa],which=get(now); son[fa][which]=son[now][which^1],father[son[fa][which]]=fa; son[now][which^1]=fa,father[fa]=now,father[now]=ofa; if(ofa) son[ofa][son[ofa][1]==fa]=now; update(fa),update(now); } inline void relax(int now,int to) { if(now!=to) relax(father[now],to); pushdown(now); } inline void splay(int now,int to) { relax(now,to); while(father[now]!=to) { if(father[father[now]]!=to) rotate(get(now)==get(father[now])?father[now]:now); rotate(now); } if(!to) root=now; } inline int pick() { if(tail) return que[tail--]; else return ++tot; } int produce(int x) { int t=pick(); key[t]=a[x]; cov[t]=-inf; rev[t]=0; lm[t]=rm[t]=mx[t]=-inf; return t; } inline int find(int now,int k) { pushdown(now); if(size[son[now][0]]>=k) return find(son[now][0],k); else if(size[son[now][0]]+1==k) return now; else return find(son[now][1],k-size[son[now][0]]-1); } int build(int l,int r) { int mid=(l+r)>>1,le=0,ri=0; if (l<mid) le=build(l,mid-1); int t=produce(mid); if (r>mid) ri=build(mid+1,r); if (le) son[t][0]=le,father[le]=t; if (ri) son[t][1]=ri,father[ri]=t; update(t); return t; } void del(int &x) { if (!x) return; que[++tail]=x; father[x]=0; del(son[x][0]); del(son[x][1]); lm[x]=rm[x]=mx[x]=-inf; x=0; } int main() { //freopen("a.in","r",stdin); n=R();m=R();a[st=1]=0;a[ed=n+2]=0; for(int i=2;i<=n+1;i++) a[i]=R(); rm[0]=lm[0]=mx[0]=-inf; root=build(1,n+2);int x,y,z,l,r; for (int i=1;i<=m;i++) { scanf("%s",s); if (s[0]=='I') { x=R(),y=R(); l=find(root,x+1); r=find(root,x+2); splay(r,0); splay(l,root); for (int j=1;j<=y;j++) a[j]=R(); int tmp=build(1,y); father[tmp]=l; son[l][1]=tmp; update(l); update(r); splay(tmp,root); } if (s[0]=='D') { x=R(),y=R(); r=find(root,x+y+1);splay(r,0); l=find(root,x);splay(l,root); del(son[l][1]); update(l); update(r); } if (s[0]=='M'&&s[2]=='K') { x=R(),y=R(),z=R(); r=find(root,x+y+1);splay(r,0); l=find(root,x);splay(l,root); cover(son[l][1],z); } if (s[0]=='R') { x=R(),y=R(); r=find(root,x+y+1);splay(r,0); l=find(root,x);splay(l,root); reverse(son[l][1]); } if (s[0]=='G') { x=R(),y=R(); r=find(root,x+y+1);splay(r,0); l=find(root,x);splay(l,root); ans=sum[son[l][1]]; printf("%d\n",ans); } if (s[0]=='M'&&s[2]=='X') { splay(ed,0); splay(st,root); ans=mx[son[st][1]]; printf("%d\n",ans); } } return 0; }