1500: [NOI2005]维修数列
Time Limit: 10 Sec Memory Limit: 64 MBSubmit: 6672 Solved: 2016
[ Submit][ Status]
Description
Input
输入文件的第1行包含两个数N和M,N表示初始时数列中数的个数,M表示要进行的操作数目。第2行包含N个数字,描述初始时的数列。以下M行,每行一条命令,格式参见问题描述中的表格。
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
非常麻烦的splay模板题!!写完之后感觉对splay操作以及Push_up() Push_down()操作有了更深的理解!
Insert(),Delete(),Reverse()操作和【BZOJ 1269】完全一致。
Make-same():在每个节点上维护一个modi的信息,如果是true说明整棵子树都被修改为a[x].data,否则没有改动
Get-sum():每个结点维护一个sum,a[x].sum=a[a[x].l].sum+a[a[x].r].sum+a[x].data
Maxsum():这个询问最为复杂,需要维护三个值: maxl;maxr;maxm。(如何维护在Push_up操作中说)
小结一下:每个结点需要维护11个值
(1)l:左儿子
(2)r:右儿子
(3)fa:父亲
(4)data:此结点所代表的数
(5)size:区间长度
(6)rev:是否被反转
(7)modi:是否被修改为其他的数
(8)sum:此区间的所有数之和
(9)maxl:从区间最左端向右延伸的最大和
(10)maxr:从区间最右端向左延伸的最大和
(11)maxm:整个区间的最大子序列和
那么本题最关键的部分就在于Push_down和Push_up操作了!!
Push_up(x):
Push_up操作就是当儿子结点有修改之后要更新父亲结点。
size和sum很好维护,说一下maxl,maxr,maxm的维护方法:
(一)maxl:分两种情况:
(1)不包含x:a[a[x].l].maxl;
(2)包含x:而包含x中如果右子树maxl>0就加上否则不加a[a[x].l].sum+a[x].data+max(0,a[a[x].r].maxl))
(二)maxr同maxl。
(三)maxm:分两种情况
(1)不包含x:max(a[a[x].l].maxm,a[a[x].r].maxm)
(2)包含x:如果左子树的maxr>0就加上,右子树的maxl>0也加上a[x].data+max(0,a[a[x].l].maxr)+max(0,a[a[x].r].maxl)
Push_down(x):
Push_down操作是指对整棵子树的根结点进行标记后,整棵子树的每一个结点都要修改。Push_down(x)后x结点的标记下传,则x结点的信息的已经维护好了,以后维护x的儿子。
分两种情况:
(1)如果a[x].rev即需要反转:把rev标记下传,交换左右儿子,交换maxl,maxr
(2)如果a[x].modi即需要修改权值:修改子树权值并把标记下传;对于当前结点的maxl,maxr,maxm,如果修改后的权值>0就抖赋值为权值*size,否则赋值为一个权值。
下面贴代码:
(标注的1、2、3后文解释)
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cmath>
#define maxx 0x3f3f3f3f
#define maxn 500010
using namespace std;
struct splay
{
int l,r,fa,size,sum,data,modi,maxl,maxr,maxm,rev;
}a[maxn];
char str[maxn];
int tot,f[maxn],root=0,n,m,pos,tot1=0,tot2=0,s[maxn];
void Push_up(int x)
{
if (!x) return;
int l=a[x].l,r=a[x].r;
a[x].size=1+a[a[x].l].size+a[a[x].r].size;
a[x].sum=a[a[x].l].sum+a[a[x].r].sum+a[x].data;
//a[x].maxl
int ma;
int pr=0,pl=0;
if (a[r].maxl>0) pr=a[r].maxl;
if (a[l].maxl>a[l].sum+a[x].data+pr) ma=a[l].maxl;
else ma=a[l].sum+a[x].data+pr;
a[x].maxl=ma;
//a[x].maxr
if (a[l].maxr>0) pl=a[l].maxr;
if (a[r].maxr>a[r].sum+a[x].data+pl) ma=a[r].maxr;
else ma=a[r].sum+a[x].data+pl;
a[x].maxr=ma;
//a[x].maxm
pl=pr=0;
if (a[l].maxm>a[r].maxm) ma=a[l].maxm;
else ma=a[r].maxm;
if (a[r].maxl>0) pr=a[r].maxl;
if (a[l].maxr>0) pl=a[l].maxr;
if (ma<pl+a[x].data+pr) ma=pl+a[x].data+pr;
a[x].maxm=ma;
}
void Push_down(int x)
{
if (!x) return;
if (a[x].rev)
{
a[x].rev=0;
swap(a[x].maxl,a[x].maxr);
swap(a[x].l,a[x].r);
a[a[x].l].rev^=1;
a[a[x].r].rev^=1;
}
if (a[x].modi)
{
a[a[x].l].data=a[a[x].r].data=a[x].data;
if (a[x].data>=0) a[x].maxl=a[x].maxr=a[x].maxm=a[x].data*a[x].size;
else a[x].maxl=a[x].maxr=a[x].maxm=a[x].data;
a[x].sum=a[x].data*a[x].size;
a[a[x].l].modi=a[a[x].r].modi=1;
a[x].modi=0;
}
}
void New_node(int &x,int fa,int key)
{
if (tot2) x=s[tot2--];
else x=++tot1;
a[x].fa=fa;
a[x].data=a[x].maxl=a[x].maxr=a[x].maxm=key;
a[x].l=a[x].r=a[x].rev=a[x].size=0;
a[x].modi=0;
}
void Build(int &x,int fa,int l,int r)
{
if (l>r) return;
int m=(l+r)>>1;
New_node(x,fa,f[m]);
Build(a[x].l,x,l,m-1);
Build(a[x].r,x,m+1,r);
Push_up(x);
}
int judge(char *str)
{
if (str[0]=='I') return 1;
if (str[0]=='D') return 2;
if (str[0]=='M')
{
if (str[2]=='K') return 3;
return 6;
}
if (str[0]=='R') return 4;
return 5;
}
void zig(int x)
{
int y=a[x].fa;
int z=a[y].fa;
Push_down(a[y].r);Push_down(a[x].l);Push_down(a[x].r); //1
a[x].fa=z,a[y].fa=x;
a[y].l=a[x].r,a[a[x].r].fa=y,a[x].r=y;
if (a[z].l==y) a[z].l=x;
else a[z].r=x;
Push_up(y);
}
void zag(int x)
{
int y=a[x].fa;
int z=a[y].fa;
Push_down(a[y].l);Push_down(a[x].l);Push_down(a[x].r);
a[x].fa=z,a[y].fa=x;
a[y].r=a[x].l,a[a[x].l].fa=y,a[x].l=y;
if (a[z].l==y) a[z].l=x;
else a[z].r=x;
Push_up(y);
}
void splay(int x,int s)
{
Push_down(x);
while (a[x].fa!=s)
{
int y=a[x].fa;
int z=a[y].fa;
if (z==s)
{
if (x==a[y].l) zig(x);
else zag(x);
break;
}
if (y==a[z].l)
{
if (x==a[y].l) zig(y),zig(x);
else zag(x),zig(x);
}
else
{
if (x==a[y].r) zag(y),zag(x);
else zig(x),zag(x);
}
}
Push_up(x);
if (s==0) root=x;
}
int Findkth(int x,int k)
{
Push_down(x);
int s=a[a[x].l].size;
if (k==s+1) return x;
if (k<=s) return Findkth(a[x].l,k);
return Findkth(a[x].r,k-s-1);
}
int Getmin(int x)
{
Push_down(x);
while (a[x].l)
x=a[x].l,Push_down(x);
return x;
}
void Insert(int pos,int tot)
{
int x=Findkth(root,pos);
splay(x,0);
x=Getmin(a[x].r);
splay(x,root);
Build(a[x].l,x,1,tot);
splay(a[x].l,0);
}
void Out_memory(int x) //2
{
if (!x) return;
s[++tot2]=x;
Out_memory(a[x].l);
Out_memory(a[x].r);
}
void Delet(int pos,int tot)
{
int x=Findkth(root,pos);
splay(x,0);
x=Findkth(root,pos+tot+1);
splay(x,root);
Out_memory(a[x].l);
a[x].l=0;
splay(x,0);
}
void Modify(int pos,int tot,int c)
{
int x=Findkth(root,pos);
splay(x,0);
x=Findkth(root,pos+tot+1);
splay(x,root);
a[a[x].l].modi=1;
a[a[x].l].data=c;
splay(a[x].l,0);
}
void Reverse(int pos,int tot)
{
int x=Findkth(root,pos);
splay(x,0);
x=Findkth(root,pos+tot+1);
splay(x,root);
a[a[x].l].rev^=1;
splay(a[x].l,0);
}
int Getsum(int pos,int tot)
{
int x=Findkth(root,pos);
splay(x,0);
x=Findkth(root,pos+tot+1);
splay(x,root);
Push_down(a[x].l);
return a[a[x].l].sum;
}
int Getmaxsum()
{
splay(1,0);
splay(2,root);
Push_down(a[2].l);
return a[a[2].l].maxm;
}
int main()
{
scanf("%d%d",&n,&m);
f[1]=f[2]=-maxx;
a[0].maxl=a[0].maxr=a[0].maxm=-maxx; //3
a[0].size=a[0].sum=0;
Build(root,0,1,2);
Push_up(root);
for (int i=1;i<=n;i++)
scanf("%d",&f[i]);
Build(a[2].l,2,1,n);
splay(a[2].l,0);
while (m--)
{
scanf("%s",str);
int q=judge(str);
if (q==1)
{
scanf("%d%d",&pos,&tot);
for (int i=1;i<=tot;i++)
scanf("%d",&f[i]);
Insert(pos+1,tot);
}
else if (q==2)
{
scanf("%d%d",&pos,&tot);
Delet(pos,tot);
}
else if (q==3)
{
int c;
scanf("%d%d%d",&pos,&tot,&c);
Modify(pos,tot,c);
}
else if (q==4)
{
scanf("%d%d",&pos,&tot);
Reverse(pos,tot);
}
else if (q==5)
{
scanf("%d%d",&pos,&tot);
printf("%d\n",Getsum(pos,tot));
}
else if (q==6)
{
printf("%d\n",Getmaxsum());
}
}
return 0;
}
ac过程好艰辛。。
注释:
1.之前写过的zigzag都是Push_down(y),Push_down(x)。这里为什么要这样做?因为在维护y结点时需要用到y的儿子结点的信息,所以此时必须先把y的儿子结点维护好
(其他题目也可以这样写,这种写法要注意的是在push_down和push_up中写:if (!x) return;)
2.本题读入数据有4000000个数字,按照常规方法a[]要开到4000000,会MLE。但是我们注意到每次Delete之后就会有一些结点标号可以重复利用,因此用s[]表示当前有哪些结点可以重复使用(即内存池),tot2表示s[]的大小;每次New_node()的时候先看内存池中有没有结点标号可以用,如果有就用原来的,否则再用新的(++tot1)。这样就不会超空间了。
3.因为维护结点信息的操作中要用到a[x].l,a[x].r的信息,如果没有左儿子或右儿子就会指向0结点,所以0结点的信息要求对其他结点的信息没有干扰,所以赋值为最小。同时注意,在Push_down() Push_up()操作中,如果x==0,立即返回,不需要维护,否则会出错。
感悟:
1.当前结点的信息被更新后,他的父亲,祖父,曾祖父。。的信息都需要被修改,只要把该结点旋到根结点就可以实现这个目标。
(直接写Push_down()Push_up()也可以,但是容易出错)
2.每次在用到儿子结点的信息时,都要把父亲结点的信息下传;
每次儿子结点的信息被更新后,要用到父亲结点的信息,都要把儿子结点的信息上传。
3.splay(x)之后,x在旋转过程中经过的点及其儿子的信息就都被维护好了