题目描述
请写一个程序,要求维护一个数列,支持以下 6 种操作:(请注意,格式栏 中的下划线‘ _ ’表示实际输入文件中的空格)
输入输出格式
输入格式:输入文件的第 1 行包含两个数 N 和 M,N 表示初始时数列中数的个数,M 表示要进行的操作数目。 第 2 行包含 N 个数字,描述初始时的数列。 以下 M 行,每行一条命令,格式参见问题描述中的表格
输出格式:对于输入数据中的 GET-SUM 和 MAX-SUM 操作,向输出文件依次打印结 果,每个答案(数字)占一行。
输入输出样例
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
说明
你可以认为在任何时刻,数列中至少有 1 个数。
输入数据一定是正确的,即指定位置的数在数列中一定存在。
50%的数据中,任何时刻数列中最多含有 30 000 个数;
100%的数据中,任何时刻数列中最多含有 500 000 个数。
100%的数据中,任何时刻数列中任何一个数字均在[-1 000, 1 000]内。
100%的数据中,M ≤20 000,插入的数字总数不超过 4 000 000 。
splay没的说。。。
(如果你真的要用 Treap 我也不阻止你。。。)
初始化
首先,对于原序列,我们不应该一个一个读入,然后插入,那么效率就是O(nlogn),而splay的常数本身就很大,所以考虑一个优化,就是把原序列一次性读入后,直接类似线段树的build,搞一个整体建树,即不断的将当前点维护的区间进行二分,到达单元素区间后,就把对应的序列值插入进去,这样,我们一开始建的树就是一个非常平衡的树,可以使后续操作的常数更小,并且建树整个复杂度只是O(2n)的。
Insert操作
其次,我们来考虑一下如何维护一个insert操作。我们可以这么做,首先如上将需要insert的区间变成节点数目为tot的平衡树,然后把k+1(注意我们将需要操作的区间右移了一个单位,所以题目所给k就是我们需要操作的k+1)移到根节点的位置,把原树中的k+2移到根节点的右儿子的位置。然后把需要insert的区间,先build成一个平衡树,把需要insert的树的根直接挂到原树中k+1的左儿子上就行了。
Delete操作
再然后,我们来考虑一下delete操作,我们同样的,把需要delete的区间变成[k+1,k+tot](注意,是删去k后面的tot个数,那么可以发现我们需要操作的原区间是[k,k+tot-1]!),然后把k号节点移到根节点的位置,把k+tot+2移到根节点的右儿子位置,然后直接把k+tot+2的左儿子的指针清为0,就把这段区间删掉了。可以发现,比insert还简单一点。
Reverse操作
接下来,这道题的重头戏就要开始了。splay的区间操作基本原理还类似于线段树的区间操作,即延迟修改,又称打懒标记。
对于翻转(reverse)操作,我们依旧是将操作区间变成[k+1,k+tot],然后把k和k+tot+1分别移到对应根的右儿子的位置,然后对这个右儿子的左儿子打上翻转标记即可。
Make-Same操作
对于Make-Same操作,我们同样需要先将需要操作的区间变成[k+1,k+tot],然后把k和k+tot+1分别移到根和右儿子的位置,然后对这个右儿子的左儿子打上修改标记即可。
Get-Sum操作
对于Get-Sum操作,我们还是将操作区间变成[k+1,k+tot],然后把k和k+tot+1分别移到根和右儿子的位置,然后直接输出这个右儿子的左儿子上的sum记录的和。
Max-Sum操作
对于这个求最大子序列的操作,即Max-Sum操作,我们不能局限于最开始学最大子序列的线性dp方法,而是要注意刚开始,基本很多书都会介绍一个分治的O(nlogn)的方法,但是由于存在O(n)的方法,导致这个方法并不受重视,但是这个方法确实很巧妙,当数列存在修改操作时,线性的算法就不再适用了。
这种带修改的最大子序列的问题,最开始是由线段树来维护,具体来说就是,对于线段树上的每个节点所代表的区间,维护3个量:lx表示从区间左端点l开始的连续的前缀最大子序列。rx表示从区间右端点r开始的连续的后缀最大子序列。mx表示这个区间中的最大子序列。
那么在合并[l,mid]和[mid+1,r]时,就类似一个dp的过程了!其中
lx[l,r]=max(lx[l,mid],sum[l,mid]+lx[mid+1,r])lx[l,r]=max(lx[l,mid],sum[l,mid]+lx[mid+1,r])
rx[l,r]=max(rx[mid+1,r],sum[mid+1,r]+rx[l,mid])rx[l,r]=max(rx[mid+1,r],sum[mid+1,r]+rx[l,mid])
mx[l,r]=max(mx[l,mid],mx[mid+1,r],lx[mid+1,r]+rx[l,mid+1])mx[l,r]=max(mx[l,mid],mx[mid+1,r],lx[mid+1,r]+rx[l,mid+1])
这个还是很好理解的。就是选不选mid的两个决策。但是其实在实现的时候,我们并不用[l,r]的二维方式来记录这三个标记,而是用对应的节点编号来表示区间,这个可以看程序,其实是个很简单的东西。
那么最大子序列这个询问操作就可以很简单的解决了,还是类比前面的方法,就是把k和k+tot+1移到对应的根和右儿子的位置,然后直接输出右儿子的左儿子上的mx标记即可
懒标记的处理
最后,相信认真看了的童鞋会有疑问,这个标记怎么下传呢?首先,我们在每次将k和k+tot+1移到对应的位置时,需要一个类似查找k大值的find操作,即找出在平衡树中,实际编号为k在树中中序遍历的编号,这个才是我们真正需要处理的区间端点编号,那么就好了,我们只需在查找的过程中下传标记就好了!(其实线段树中也是这么做的),因为我们所有的操作都需要先find一下,所以我们可以保证才每次操作的结果计算出来时,对应的节点的标记都已经传好了。而我们在修改时,直接修改对应节点的记录标记和懒标记,因为我们的懒标记记录的都是已经对当前节点产生贡献,但是还没有当前节点的子树区间产生贡献!然后就是每处有修改的地方都要pushup一下就好了。
细节处理
另外,由于本题数据空间卡的非常紧,我们就需要用时间换空间,直接开4000000*logm的数据是不现实的,但是由于题目保证了同一时间在序列中的数字的个数最多是500000,所以我们考虑一个回收机制,把用过但是已经删掉的节点编号记录到一个队列或栈中,在新建节点时直接把队列中的冗余编号搞过来就好了。
附上代码:
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<queue>
#define MAXN 1000010
#define MAX 999999999
using namespace std;
queue<int> q;
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(q.empty())rt=size++;
else{
rt=q.front();
q.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;
//pushdown(x);pushdown(rt);
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;
q.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);
}
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);
}
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);
}
int main(){
int x,y,k;
char ch[20];
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);
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;
}
}
}
return 0;
}