bzoj1500 [NOI2005]维修数列解题报告

Bzoj 1500: [NOI2005]维修数列 解题报告

题目原文

Time Limit: 10 Sec   Memory Limit: 64 MB
Submit: 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

Sample Output

-1
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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值