BZOJ1500: [NOI2005]维修数列
Description
Input
输入的第1 行包含两个数N 和M(M ≤20 000),N 表示初始时数列中数的个数,M表示要进行的操作数目。
第2行包含N个数字,描述初始时的数列。
以下M行,每行一条命令,格式参见问题描述中的表格。
任何时刻数列中最多含有500 000个数,数列中任何一个数字均在[-1 000, 1 000]内。
插入的数字总数不超过4 000 000个,输入文件大小不超过20MBytes。
Output
对于输入数据中的GET-SUM和MAX-SUM操作,向输出文件依次打印结果,每个答案(数字)占一行。
Sample Input
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
Sample Output
10
1
10
题解Here!
区间翻转,当然 Splay !
如果你真的要用 Fhq Treap 我也不会拦着。。。
初始化
首先,对于原序列,我们一个一个读入,然后插入,那么效率就是O(nlogn)。
而splay的常数本身就很大,对于1s的时限比较紧,所以考虑一个优化:
就是把原序列一次性读入后,直接类似线段树的buildtree,搞一个整体建树,即不断的将当前点维护的区间进行二分,到达单元素区间后,就把对应的序列值插入进去,即:
mid=l+r>>1; lson=buildtree(l,mid-1); rt=newnode(val[rt]); rson=buildtree(mid+1,r); pushup(rt);
这样,我们一开始建的树就是一个非常平衡的树,可以使后续操作的常数更小,并且建树整个复杂度只是O(2n)的。
注意添加头尾的哨兵节点。
Insert操作
不用想,不可能是边读入边插入。那怎么办?
我们可以这么做,先把k+1(注意我们将需要操作的区间右移了一个单位,所以题目所给k,我们需要操作k+1)移到根节点的位置,把原树中的k+2移到根节点的右儿子的位置。
然后把需要insert的区间,建成一个平衡树,将其根直接连到原树中k+2的左儿子上就行了。
Delete操作
同样,不可能是边读入边删除。
我们同样的,把需要delete的区间变成 [ k+1 , k+tot ](注意,是删去k后面的tot个数,那么可以发现我们需要操作的原区间是[k,k+tot-1]);
然后把 k 号节点移到根节点的位置,把 k+tot+2 移到根节点的右儿子位置,然后直接把 k+tot+2 的左子树全部清为0,就把这段区间删掉了。
可以发现,比insert还简单一点。
Reverse操作
接下来,这道题的重头戏就要开始了——为什么一定要用 Splay 。
splay的区间操作基本原理还类似于线段树的区间操作,即延迟修改,又称打懒惰标记。
我们依旧是将操作区间变成 [ k+1 , k+tot ] ,然后把 k 和 k+tot+1 分别移到根与其右儿子的位置,然后对这个右儿子的左儿子打上翻转标记即可。
Make-Same操作
我们同样需要先将需要操作的区间变成 [k+1 , k+tot ] ,然后把 k 和 k+tot+1 分别移到根和右儿子的位置,然后对这个右儿子的左儿子打上修改标记即可。
Get-Sum操作
我们还是将操作区间变成 [ k+1 , k+tot ] ,然后把 k 和 k+tot+1 分别移到根和右儿子的位置,然后直接输出这个右儿子的左儿子上记录的和值。
Max-Sum操作
对于这个求最大子序列的操作,即Max-Sum操作,我们不能局限于最开始学最大子序列的线性dp方法,而是要注意刚开始,基本很多书都会介绍一个分治的O(nlogn)的方法,但是由于存在O(n)的方法,导致这个方法并不受重视,但是这个方法确实很巧妙,当数列存在修改操作时,线性的算法就不再适用了。
这种带修改的最大子序列的问题,最开始是由线段树来维护,具体来说就是,对于线段树上的每个节点所代表的区间,维护3个量:
suml 表示从区间左端点 l 开始的连续的前缀最大子序列。
sumr 表示从区间右端点 r 开始的连续的后缀最大子序列。
sum 表示这个区间中的最大子序列。
那么在合并[l,mid]和[mid+1,r]时,就类似一个dp的过程了!其中
suml[l,r]=max(suml[l,mid],w[l,mid]+sum[mid+1,r]) sumr[l,r]=max(sumr[mid+1,r],w[mid+1,r]+sumr[l,mid]) sum[l,r]=max(sum[l,mid],sum[mid+1,r],suml[mid+1,r]+sumr[l,mid+1]) //w[l,r]是区间和值
这个还是很好理解的,就是选不选mid的两个决策。
但是其实在实现的时候,我们并不用 [ l , r ] 的二维方式来记录这三个标记,而是用对应的节点编号来表示区间,这个可以看程序,其实是个很简单的东西。
那么最大子序列这个询问操作就可以很简单的解决了,还是类比前面的方法,就是把 k 和 k+tot+1 移到对应的根和右儿子的位置,然后直接输出右儿子的左儿子上的 sum 标记即可。
懒惰标记
最后,相信认真看了的童鞋会有疑问,这个标记怎么下传呢?
首先,我们在每次将 k 和 k+tot+1 移到对应的位置时,需要一个查找 k大值 的 kth 操作,这个才是我们真正需要处理的区间端点编号。
那么就好了,我们只需在查找的过程中下传标记 pushdown 一下就好了!(其实线段树中也是这么做的)
因为我们所有的操作都需要先 kth 一下,所以我们可以保证才每次操作的结果计算出来时,对应的节点的标记都已经传好了。
而我们在修改时,直接修改对应节点的记录标记和懒惰标记,因为我们的懒惰标记记录的都是已经对当前节点产生贡献,但是还没有当前节点的子树区间产生贡献!
然后就是每处有修改的地方都要 pushup 一下就好了。
附:pushdown的写法
不少人会将pushdown写成:
if(lson)a[lson].flag^=1; if(rson)a[rson].flag^=1; a[rt].flag^=1; swap(lson,rson);
但是这是不对的!应该:
if(a[rt].flag){ if(a[rt].son[0]){ a[a[rt].son[0]].flag^=1; swap(a[a[rt].son[0]].son[0],a[a[rt].son[0]].son[1]); } if(a[rt].son[1]){ a[a[rt].son[1]].flag^=1; swap(a[a[rt].son[1]].son[0],a[a[rt].son[1]].son[1]); } a[rt].flag^=1; }
虽然我也不知道为什么。。。
我只知道要类似线段树的pushdown。。。
细节处理
由于本题数据空间卡的非常紧,我们就需要用时间换空间,直接开 4000000*logm 的数据是不现实的。
但是由于题目保证了同一时间在序列中的数字的个数最多是500000,所以我们考虑一个回收机制:
把用过但是已经删掉的节点编号记录到一个队列中,在新建节点时直接把队列中的冗余编号搞过来就好了。
吐槽:这种题就是毒瘤,考场上估计只有国家队队爷才有时间敲正解。。。
附代码:
#include<iostream> #include<algorithm> #include<cstdio> #include<queue> #define MAXN 500010 #define MAX 999999999 using namespace std; queue<int> point; int n,m,size=1,root,val[MAXN]; struct node{ int f,s,flag,set,son[2]; int v,w,sum,suml,sumr; }a[MAXN]; inline int read(){ int date=0,w=1;char c=0; while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();} while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();} return date*w; } inline void clean(int rt){ a[rt].son[0]=a[rt].son[1]=a[rt].f=a[rt].s=a[rt].flag=a[rt].v=a[rt].w=0; a[rt].sum=a[rt].suml=a[rt].sumr=-MAX; } inline void pushup(int rt){ if(!rt)return; a[rt].s=a[a[rt].son[0]].s+a[a[rt].son[1]].s+1; a[rt].w=a[a[rt].son[0]].w+a[a[rt].son[1]].w+a[rt].v; a[rt].suml=max(a[a[rt].son[0]].suml,a[a[rt].son[0]].w+a[rt].v+max(0,a[a[rt].son[1]].suml)); a[rt].sumr=max(a[a[rt].son[1]].sumr,a[a[rt].son[1]].w+a[rt].v+max(0,a[a[rt].son[0]].sumr)); a[rt].sum=max(a[rt].v+max(0,a[a[rt].son[0]].sumr)+max(0,a[a[rt].son[1]].suml),max(a[a[rt].son[0]].sum,a[a[rt].son[1]].sum)); } inline void pushdown(int rt){ if(!rt)return; if(a[rt].set!=-MAX){ if(a[rt].son[0]){ a[a[rt].son[0]].set=a[a[rt].son[0]].v=a[rt].set; a[a[rt].son[0]].w=a[rt].set*a[a[rt].son[0]].s; a[a[rt].son[0]].suml=a[a[rt].son[0]].sumr=a[a[rt].son[0]].sum=max(a[a[rt].son[0]].set,a[a[rt].son[0]].w); } if(a[rt].son[1]){ a[a[rt].son[1]].set=a[a[rt].son[1]].v=a[rt].set; a[a[rt].son[1]].w=a[rt].set*a[a[rt].son[1]].s; a[a[rt].son[1]].suml=a[a[rt].son[1]].sumr=a[a[rt].son[1]].sum=max(a[a[rt].son[1]].set,a[a[rt].son[1]].w); } a[rt].set=-MAX; a[rt].flag=0; } if(a[rt].flag){ if(a[rt].son[0]){ a[a[rt].son[0]].flag^=1; swap(a[a[rt].son[0]].suml,a[a[rt].son[0]].sumr); swap(a[a[rt].son[0]].son[0],a[a[rt].son[0]].son[1]); } if(a[rt].son[1]){ a[a[rt].son[1]].flag^=1; swap(a[a[rt].son[1]].suml,a[a[rt].son[1]].sumr); swap(a[a[rt].son[1]].son[0],a[a[rt].son[1]].son[1]); } a[rt].flag^=1; } } inline int newnode(int x){ int rt; if(point.empty())rt=size++; else{ rt=point.front(); point.pop(); } a[rt].v=x; a[rt].suml=a[rt].sumr=a[rt].sum=-MAX; a[rt].flag=0;a[rt].set=-MAX; return rt; } inline void turn(int rt,int k){ int x=a[rt].f,y=a[x].f; a[x].son[k^1]=a[rt].son[k]; if(a[rt].son[k])a[a[rt].son[k]].f=x; a[rt].f=y; if(y)a[y].son[a[y].son[1]==x]=rt; a[x].f=rt; a[rt].son[k]=x; pushup(x);pushup(rt); } void splay(int rt,int ancestry){ while(a[rt].f!=ancestry){ int x=a[rt].f,y=a[x].f; if(y==ancestry)turn(rt,a[x].son[0]==rt); else{ int k=a[y].son[0]==x?1:0; if(a[x].son[k]==rt){turn(rt,k^1);turn(rt,k);} else{turn(x,k);turn(rt,k);} } } if(ancestry==0)root=rt; } int kth(int rt,int k){ if(a[rt].s<k)return 0; while(1){ pushdown(rt); int y=a[rt].son[0]; if(k>a[y].s+1){ rt=a[rt].son[1]; k-=a[y].s+1; } else if(k<=a[y].s)rt=y; else return rt; } } int buildtree(int l,int r){ if(l>r)return 0; int rt,mid=l+r>>1,lson=0,rson=0; lson=buildtree(l,mid-1); rt=newnode(val[mid]); rson=buildtree(mid+1,r); a[rt].son[0]=lson; a[rt].son[1]=rson; if(lson)a[lson].f=rt; if(rson)a[rson].f=rt; pushup(rt); return rt; } inline void split(int front,int next){ splay(front,0);splay(next,front); } inline void insert(int rt,int x,int y){ int front=kth(rt,x+1),next=kth(rt,x+2); split(front,next); int k=a[next].son[0]; rt=buildtree(1,y); a[next].son[0]=rt;a[rt].f=next; pushup(next);pushup(front); } void delete_x(int rt){ if(!rt)return; point.push(rt); if(a[rt].son[0])delete_x(a[rt].son[0]); if(a[rt].son[1])delete_x(a[rt].son[1]); clean(rt); } inline void remove(int rt,int l,int r){ int front=kth(rt,l),next=kth(rt,r+2); split(front,next); int k=a[next].son[0]; if(k)delete_x(k); a[next].son[0]=0; pushup(next);pushup(front); } inline void reverge(int rt,int l,int r){ int front=kth(rt,l),next=kth(rt,r+2); split(front,next); int k=a[next].son[0]; if(!k)return; a[k].flag^=1; swap(a[k].son[0],a[k].son[1]); swap(a[k].suml,a[k].sumr); pushup(next);pushup(front); } inline void same(int rt,int l,int r,int x){ int front=kth(rt,l),next=kth(rt,r+2); split(front,next); int k=a[next].son[0]; if(!k)return; a[k].set=a[k].v=x; a[k].w=x*a[k].s; a[k].suml=a[k].sumr=a[k].sum=max(x,a[k].w); pushup(next);pushup(front); } void work(){ int x,y,k; char ch[20]; while(m--){ scanf("%s",ch); switch(ch[0]){ case 'I':{ x=read();y=read(); for(int i=1;i<=y;i++)val[i]=read(); insert(root,x,y); n+=y; break; } case 'D':x=read();y=read();n-=y;remove(root,x,x+y-1);break; case 'R':x=read();y=read();reverge(root,x,x+y-1);break; case 'G':{ x=read();y=read(); int front=kth(root,x),next=kth(root,x+y+1); split(front,next); int k=a[next].son[0]; printf("%d\n",a[k].w); break; } case 'M':{ if(ch[4]=='-'){ x=read();y=read();k=read(); same(root,x,x+y-1,k); } else{ x=1;y=n; int front=kth(root,x),next=kth(root,x+y+1); split(front,next); int k=a[next].son[0]; printf("%d\n",a[k].sum); } break; } } } } void init(){ n=read();m=read(); for(int i=1;i<=n;i++)val[i]=read(); val[0]=val[n+1]=0; clean(0);clean(n+1); root=buildtree(0,n+1); } int main(){ init(); work(); return 0; }