BZOJ 1500/Luogu 2042 - 维修数列 - [NOI2005][Splay]
题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=1500
题目链接:https://www.luogu.org/problemnew/show/P2042
Description
请写一个程序,要求维护一个数列,支持以下 6 种操作: 请注意,格式栏 中的下划线‘ _ ’表示实际输入文件中的空格
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
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
Sample Output
-1
10
1
10
HINT
题解:
Splay模板题。
其中,关于如何搞定求区间最大连续子列和的问题,可以参考线段树的做法:UVALive 3938 - "Ray, Pass me the dishes!" - [最大连续子列和+线段树](通过分治+最大前缀和+最大后缀和共同维护得到最大连续子列和)(感慨一下,已经想不起是哪个时候做的这道题了,时光飞逝啊……)。
关于区间翻转,则是Splay老生常谈的事情了,一个 $rev$ 标记搞定。
AC代码:
#include<bits/stdc++.h> using namespace std; const int INF=0x3f3f3f3f; const int maxn=5e5+10; int n,m; int a[maxn]; /******************************** splay - st ********************************/ #define Key_value ch[ch[root][1]][0] int root,nodecnt; int par[maxn],ch[maxn][2]; int key[maxn],sum[maxn],siz[maxn]; int mxpre[maxn],mxsuf[maxn],mxsub[maxn]; //最大前缀和,最大后缀和,最大连续子列和 bool alt[maxn],rev[maxn]; //修改标记,反转标记 int pool[maxn],poolsize; //节点回收 void NewNode(int &x,int p,int k) { if(poolsize>0) x=pool[--poolsize]; else x=++nodecnt; par[x]=p; ch[x][0]=ch[x][1]=0; key[x]=sum[x]=k; mxpre[x]=mxsuf[x]=mxsub[x]=k; siz[x]=1; alt[x]=rev[x]=0; } void Update_Rev(int x) { if(x==0) return; swap(ch[x][0],ch[x][1]); swap(mxpre[x],mxsuf[x]); rev[x]^=1; } void Update_Alt(int x,int val) { if(x==0) return; key[x]=val; sum[x]=siz[x]*val; mxpre[x]=mxsuf[x]=mxsub[x]=max(val,val*siz[x]); alt[x]=1; } void Pushup(int x) { int ls=ch[x][0],rs=ch[x][1]; siz[x]=siz[ls]+siz[rs]+1; sum[x]=sum[ls]+sum[rs]+key[x]; mxpre[x]=max(mxpre[ls],sum[ls]+key[x]+max(0,mxpre[rs])); mxsuf[x]=max(mxsuf[rs],max(0,mxsuf[ls])+key[x]+sum[rs]); mxsub[x]=max(max(mxsub[ls],mxsub[rs]),max(0,mxsuf[ls])+key[x]+max(0,mxpre[rs])); } void Pushdown(int x) { if(rev[x]) { Update_Rev(ch[x][0]); Update_Rev(ch[x][1]); rev[x]=0; } if(alt[x]) { Update_Alt(ch[x][0],key[x]); Update_Alt(ch[x][1],key[x]); alt[x]=0; } } void Rotate(int x,int type) //旋转,0为左旋zag,1为右旋zig { int y=par[x]; ch[y][!type]=ch[x][type]; par[ch[x][type]]=y; if(par[y]) ch[par[y]][(ch[par[y]][1]==y)]=x; par[x]=par[y]; ch[x][type]=y; par[y]=x; Pushup(y); Pushup(x); } void Splay(int x,int goal) { while(par[x]!=goal) { if(par[par[x]]==goal) Rotate(x,ch[par[x]][0]==x); //左孩子zig,右孩子zag else { int y=par[x]; int type=(ch[par[y]][0]==y); //type=0,y是右孩子;type=1,y是左孩子 if(ch[y][type]==x) { Rotate(x,!type); Rotate(x,type); } else { Rotate(y,type); Rotate(x,type); } } } if(goal==0) root=x; } int Get_Kth(int x,int k) //得到第k个节点 { Pushdown(x); int t=siz[ch[x][0]]+1; if(t==k) return x; if(t>k) return Get_Kth(ch[x][0],k); else return Get_Kth(ch[x][1],k-t); } void Build(int &x,int l,int r,int par) //建树,先建立中间结点,再建两端的方法 { if(l>r) return; int mid=(l+r)/2; NewNode(x,par,a[mid]); Build(ch[x][0],l,mid-1,x); Build(ch[x][1],mid+1,r,x); Pushup(x); } void Init() //初始化,前后各加一个空节点 { root=nodecnt=poolsize=0; par[0]=ch[0][0]=ch[0][1]=0; key[0]=sum[0]=siz[0]=0; alt[0]=rev[0]=0; mxpre[0]=mxsuf[0]=mxsub[0]=-INF; NewNode(root,0,-INF); //头部加入一个空位 NewNode(ch[root][1],root,-INF); //尾部加入一个空位 Build(Key_value,1,n,ch[root][1]); Pushup(ch[root][1]); Pushup(root); } void Insert(int p,int tot) { for(int i=1;i<=tot;i++) scanf("%d",&a[i]); Splay(Get_Kth(root,p+0+1),0); //p伸展到根 Splay(Get_Kth(root,p+1+1),root); //p的后继p+1伸展到根的右孩子 Build(Key_value,1,tot,ch[root][1]); Pushup(ch[root][1]); Pushup(root); } void Collect(int x) //回收节点x统领的子树 { if(x==0) return; pool[poolsize++]=x; Collect(ch[x][0]); Collect(ch[x][1]); } void Delete(int p,int tot) { Splay(Get_Kth(root,p-1+1),0); //伸展到根 Splay(Get_Kth(root,p+tot+1),root); //伸展到根的右孩子 Collect(Key_value); par[Key_value]=0; Key_value=0; Pushup(ch[root][1]); Pushup(root); } void Alter(int p,int tot,int c) //修改[p,p+tot)为k { Splay(Get_Kth(root,p-1+1),0); //伸展到根 Splay(Get_Kth(root,p+tot+1),root); //伸展到根的右孩子 Update_Alt(Key_value,c); Pushup(ch[root][1]); Pushup(root); } void Reverse(int p,int tot) //反转[p,p+tot)区间 { Splay(Get_Kth(root,p-1+1),0); Splay(Get_Kth(root,p+tot+1),root); Update_Rev(Key_value); Pushup(ch[root][1]); Pushup(root); } int Get_Sum(int p,int tot) { Splay(Get_Kth(root,p-1+1),0); Splay(Get_Kth(root,p+tot+1),root); return sum[Key_value]; } int Get_MaxSub(int p,int tot) { Splay(Get_Kth(root,p-1+1),0); Splay(Get_Kth(root,p+tot+1),root); return mxsub[Key_value]; } /******************************** splay - ed ********************************/ int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d",&a[i]); Init(); char op[12]; int pos,tot,c; while(m--) { scanf("%s",op); switch(op[0]&op[1]|op[2]) { case ('I'&'N'|'S'): scanf("%d%d",&pos,&tot); Insert(pos,tot); break; case ('D'&'E'|'L'): scanf("%d%d",&pos,&tot); Delete(pos,tot); break; case ('M'&'A'|'K'): scanf("%d%d%d",&pos,&tot,&c); Alter(pos,tot,c); break; case ('R'&'E'|'V'): scanf("%d%d",&pos,&tot); Reverse(pos,tot); break; case ('G'&'E'|'T'): scanf("%d%d",&pos,&tot); printf("%d\n",Get_Sum(pos,tot)); break; case ('M'&'A'|'X'): printf("%d\n",Get_MaxSub(1,siz[root]-2)); break; } } }