Bzoj 1500: [NOI2005]维修数列 解题报告
题目原文
Time Limit: 10 Sec Memory Limit: 64 MBSubmit: 10429 Solved: 3218
[ Submit][ Status][ Discuss]
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
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
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
10
1
10
HINT
分析
开门见山,这是一道平衡树题目,我只会用splay,据说这道题还能用treap解决(Orz),此处只介绍splay的做法,splay的基础知识在此不再展开,只介绍针对这道题的操作。
k:节点在区间中的序号(第几个数),v:结点的权值,maxsum:以此节点为根的子树所对应区间的最大子序列和,lsum:包含区间左端点的最大序列和,rsum:包含区间右端点的最大序列和,totsum:此区间的和(所有数加起来),dk:对k值打的标记,dv:对v值打的标记,表示这个区间的所有值都修改为dv,reverse布尔型变量,表示这个区间是否被翻转,size:以当前节点为根的子树的大小(节点数目)。
首先,要建一个MIN结点和一个MAX结点,分别代表区间的两端,这样在操作中可以避免一些麻烦,但注意这两个点的权值都为0,否则会影响区间和的查询。
Insert:只需把pos点splay到根,pos+1splay到根的右儿子,因为pos+1是pos的后继,因此pos+1一定没有左儿子,所以先给pos+1的dk打上增加tot的标记,再把标记下放,最后再把新建成的树挂在其左儿子的位置上。此处插入数据有个问题,已知你插入的数据的k一定是递增的,所以传统的插入会导致该数据连成一条链,因此我们采用分治的方法,取区间的mid作为根节点,然后递归处理左右子树,这样就能够建立起一棵深度为logtot的完全平衡的二叉查找树。
Delete:只需把pos-1旋转到根,pos+tot旋转到根的右子节点,删除pos+tot的左儿子,再将pos+tot打上-tot的标记。
Make-same:把pos-1 Splay到根,把pos+tot Splay到根节点右边,将pos+tot的左儿子的dv修改成c。
Reverse:把pos-1转到根,pos+tot转到根节点右边,将pos+tot的reverse标记取反。
Get-sum:把pos-1转到根,pos+tot转到根的右边,输出pos+tot左儿子的totsum。
Max-sum:把MIN转到根,MAX转到MIN的右边,输出MAX左儿子的maxsum值。
以上是很好想的,这道题的重点在于怎样将标记下放以及怎样计算lsum、rsum、totsum、maxsum以及怎样翻转。
不多说,直接给出计算公式:
lsum:设左右子树分别为l、r,当前节点为x,则x->lsum=max{ x->l->lsum , [x->l->totsum + x->v + max(0,x->r->lsum)] }
rsum:x->rsum=max{ x->r->rsum , [x->r->totsum + max(0,x->v +x->l->rsum)] }
maxsum:max{ x->l->maxsum , x->r->maxsum , [ x->v + max(0,x->l->rsum) + max(0,x->r->lsum) ] }
totsum:x->totsum = x->l->totsum + x->v + x->r->totsum
size:x->size = x->l->size + x->r->size + 1
翻转操作:首先修改x->k,x->k = x->k - x->l->size + x->r->size,然后给左右儿子打标记,可以先不考虑左右儿子的翻转,先认为左右儿子区间调换了位置,将区间整体平移,x->l->dk += 1 + x->r->size,x->r->dk-=1+x->l->size,翻转操作结束
还有一个问题就是,题目中说任何时刻区间中做多有500 000个数,大约是19MB空间,而插入数据最多有4 000 000,大约是152MB空间,后者会爆内存的,像我这样用指针和new操作节点,删除很不方便,你想想,我每次都是删除一棵子树,我只能把边断开,而不能把节点删去,我总不能用O(N)的时间再去一个一个删节点吧,本来nlogn就很擦边,常数还很大,不可能再去用O
(N)的时间删节点,由此我冥思苦想,想到一个解决方案:在程序最开始先建立一棵空树,什么信息也没有,但有500 000个节点,每次要取节点就从上面取,每次删除的树直接挂上去,取的时间复杂度和挂的时间复杂度都是O(1)的,这样我就能在不影响时间复杂度的情况下,保证最多同时存在500 000个节点。这个方法不仅适用于此题,所有平衡树的题都可以适用,有人觉得这没什么卵用,因为你完全可以开500 000的数组,也能保证空间,但众所周知指针写起来比数组方便多了,这种方法可以让你在用指针的情况下,还可以控制数据的大小,真是个好东西(程序中详见get和ins函数)。
吐槽一下:这道题的原题其实叫“维护数列”
代码如下:
//bzoj1500[NOI2005]维修数列 Splay
#include<cstdio>
#include<cstring>
#include<algorithm>
#define inf 0x3f3f3f3f
using namespace std;
int N, M, cnt, c[500050];
struct NODE
{
int lsum, rsum, totsum, maxsum, size, dk, dv, v, k; bool rev;
NODE *l, *r, *f;
void init(int kk, int vv){k=kk;v=vv;size=1;dv=inf;totsum=lsum=rsum=maxsum=v;
l=r=f=0;dk=rev=0;}
}*ROOT, *MIN, *MAX, *P;
int read()
{
int x=0; bool fu=false;
char c=getchar();
while((c<'0'||c>'9')&&c!='-')c=getchar();
if(c=='-')fu=true,c=getchar();
while(c>='0' && c<='9')x=x*10+c-'0',c=getchar();
return (fu?-x:x);
}
void swap(int &a, int &b)
{int t=a; a=b; b=t;}
void pushdown(NODE *x)
{
if(x->dv<inf)
{
x->v=x->dv; x->totsum=x->v*x->size;
x->lsum=x->rsum=x->maxsum=max(x->v,x->totsum);
if(x->l)x->l->dv=x->dv; if(x->r)x->r->dv=x->dv;
x->dv=inf;
}
if(x->dk)
{
x->k+=x->dk;
if(x->l)x->l->dk+=x->dk; if(x->r)x->r->dk+=x->dk;
x->dk=0;
}
if(x->rev)
{
int sl=0, sr=0;
if(x->l)sl=x->l->size,x->l->rev=!(x->l->rev);
if(x->r)sr=x->r->size,x->r->rev=!(x->r->rev);
x->k=x->k-sl+sr;
if(x->l)x->l->dk+=sr+1;
if(x->r)x->r->dk+=-sl-1;
swap(x->l,x->r);
swap(x->lsum,x->rsum);
x->rev=false;
}
}
void update(NODE *x)
{
pushdown(x);
if(x->l)pushdown(x->l);
if(x->r)pushdown(x->r);
int t;
x->size=1;
if(x->l)x->size+=x->l->size; if(x->r)x->size+=x->r->size;
x->totsum=x->v;
if(x->l)x->totsum+=x->l->totsum; if(x->r)x->totsum+=x->r->totsum;
t=x->v;
if(x->l)t+=max(0,x->l->rsum);if(x->r)t+=max(0,x->r->lsum);
if(x->l)t=max(t,x->l->maxsum);if(x->r)t=max(t,x->r->maxsum);
x->maxsum=t;
if(x->l)
{
t=max(x->l->lsum,x->l->totsum+x->v);
if(x->r)t=max(t,x->l->totsum + x->v + max(0,x->r->lsum) );
}
else if(x->r)t=x->v+max(0,x->r->lsum);
x->lsum=t;
if(x->r)
{
t=max(x->r->rsum,x->v+x->r->totsum);
if(x->l)t=max(t,x->r->totsum + x->v + max(0,x->l->rsum));
}
else if(x->l)t=x->v+max(0,x->l->rsum);
x->rsum=t;
}
void zig(NODE *x)
{
NODE *y=x->f;
pushdown(y),pushdown(x);
if(y->f->l==y)y->f->l=x; else y->f->r=x;
x->f=y->f;
y->l=x->r; if(x->r)x->r->f=y;
x->r=y;y->f=x;
update(y);update(x);
}
void zag(NODE *x)
{
NODE *y=x->f;
pushdown(y),pushdown(x);
if(y->f->l==y)y->f->l=x; else y->f->r=x;
x->f=y->f;
y->r=x->l; if(x->l)x->l->f=y;
x->l=y;y->f=x;
update(y);update(x);
}
void splay(NODE *x, NODE *F)
{
while(x->f!=F)
{
NODE *y=x->f;
if(y->f==F)
if(y->l==x)zig(x);
else zag(x);
else
if(y->f->l==y)
if(y->l==x)zig(y),zig(x);
else zag(x),zig(x);
else
if(y->r==x)zag(y),zag(x);
else zig(x),zag(x);
}
}
NODE* find(NODE *x, int k)
{
NODE *t;
pushdown(x);
if(x->k==k)t=x;
if(x->k>k)t=find(x->l,k);
if(x->k<k)t=find(x->r,k);
update(x);
return t;
}
NODE* get(int k, int v)
{
NODE *t;
t=P;P=P->f;
if(P->l==t)P->l=0;else P->r=0;
while(P->l||P->r)
if(P->l)P=P->l;else P=P->r;
t->init(k,v);
return t;
}
void ins(NODE *t)
{
P->l=t;t->f=P;
while(P->l||P->r)
if(P->l)P=P->l;else P=P->r;
}
NODE* build(int l, int r)
{
int mid=(l+r)>>1; NODE *p=get(mid,c[mid]);
if(l==r)return p;
if(l!=mid)p->l=build(l,mid-1),p->l->f=p;
p->r=build(mid+1,r),p->r->f=p;
update(p);
return p;
}
int main()
{
char s[100], ch;
int x, i, pos, tot, j;
NODE *p, *t;
P=new NODE;
for(i=2;i<=500005;i++)
{
t=new NODE;
t->init(0,0);
t->f=P;P->r=t;
P=t;
}
N=read();M=read();
{
cnt=0;
MIN=get(0,0);
MAX=get(1,0);
ROOT=get(123,0);
ROOT->l=MIN;MIN->f=ROOT;
MIN->r=MAX;MAX->f=MIN;
for(i=1;i<=N;i++)c[i]=read();
MAX->k+=N;
MAX->l=build(1,N);MAX->l->f=MAX;
update(MAX);update(MIN);
for(i=1;i<=M;i++)
{
scanf("%s",s);
switch(s[0])
{
case 'I':
pos=read();tot=read();
for(j=pos+1;j<=pos+tot;j++)c[j]=read();
p=find(ROOT->l,pos);t=find(ROOT->l,pos+1);
splay(p,ROOT);splay(t,p);
t->dk+=tot;pushdown(t);
t->l=build(pos+1,pos+tot);t->l->f=t;
update(t);update(p);
break;
case 'D':
pos=read();tot=read();if(!tot)break;
p=find(ROOT->l,pos-1);t=find(ROOT->l,pos+tot);
splay(p,ROOT);splay(t,p);
ins(t->l);t->l=0;
t->dk-=tot;pushdown(t);
update(t);update(p);
break;
case 'R':
pos=read();tot=read();if(!tot)break;
p=find(ROOT->l,pos-1);t=find(ROOT->l,pos+tot);
splay(p,ROOT);splay(t,p);
update(p);
t->l->rev=!(t->l->rev);
break;
case 'G':
pos=read();tot=read();if(!tot){printf("0\n");break;}
p=find(ROOT->l,pos-1);t=find(ROOT->l,pos+tot);
splay(p,ROOT);splay(t,p);
printf("%d\n",t->l->totsum);
break;
case 'M':
if(s[2]=='K')
{
pos=read();tot=read();c[0]=read();if(!tot)break;
p=find(ROOT->l,pos-1);t=find(ROOT->l,pos+tot);
splay(p,ROOT);splay(t,p);
t->l->dv=c[0];pushdown(t->l);
update(t);update(p);
break;
}
splay(MIN,ROOT);splay(MAX,MIN);
pushdown(MAX->l);printf("%d\n",MAX->l->maxsum);
}
}
}
return 0;
}