Splay Tree(伸展树)[NOI2005]维修数列

伸展树

        概述:不同于线段树的以空间换取时间,用多余的节点去存储多余的信息,来达到降低时间复杂度。SplayTree基于一种更简单的思想,为了使整个查找时间更小,被查频率高的那些条目就应当经常处于靠近树根的位置。于是想到设计一个简单方法,在每次查找之后对树进行重构,把被查找的条目搬移到离树根近一些的地方。SplayTree应运而生。SplayTree是一种自调整形式的二叉查找树,它会沿着从某个节点到树根之间的路径,通过一系列的旋转把这个节点搬移到树根去。
        Splay最基本的操作是旋转,当然前提是在旋转之后,整体仍然是有序的(即中序遍历的结果不变),分为左旋或者右旋,如果要旋转的点x是其父亲结点左儿子,那么执行的是右旋,反之左旋,简单地说就是旋转节点x和它父亲相连的那条边。假如我们定义这棵二叉搜索树父亲节点总是大于它的左儿子,小于它的右儿子。设当前要旋转的节点是x,x节点是父亲是y,y的父亲是z。右旋操作是这样的,将x的右儿子接到y的左儿子上,将y接到x的右儿子上,如果y是z的左儿子,那么x接到z的左儿子,反之,接到右儿子。容易得出这样做的结果使得整棵树仍然是有序的。
        将节点x一路旋转到根节点也就重复执行基本的左旋右旋,这也是Splay操作。不过,为了维护树的平衡,Splay操作通常分三种情况:
                第一种情况:如果节点z已经是树根,则旋转连接x和y的边。(这种情况是最后一步)
                第二种情况:如果z不是树根,而且x和y本身都是左孩子或者都是右孩子,则先旋转连接y和z,然后再旋转连接x和y的边。
                第三种情况:如果z不是树根,而且x是左儿子,y是右儿子,或者相反,则先旋转连接x和y的边,再旋转连接x和z的边。
        在节点x处进行splay操作的时间是和查找x所需的时间成比例的。这样做的结果是不单是把x搬移到了树根,而且还把查找路径上的每个节点的深度都大致减掉了一半。
        在处理区间问题的时候,通常在区间首尾各加入一个多余的节点,这样就不用特判处理的区间端点前面是否还有结点,后面还有没有结点……其实即使不是处理这样的区间问题,多加入一个多余的节点,使树中至少有一个节点,也可以减少很多麻烦和WA……
/*
http://www.lydsy.com/JudgeOnline/problem.php?id=1500
*/
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

#define LL(x) (ch[x][0])
#define RR(x) (ch[x][1])
#define MID(a,b) (a+((b-a)>>1))
#define Kt (ch[ ch[Rt][1] ][0])
#define INF (1<<30)

const int N=5e5+100;

int a[N];

struct SplayTree
{
	int Rt,top1,top2;
	int pre[N],sz[N],ch[N][2],stk[N],que[N];

	int same[N],flag[N],valu[N],flip[N],sum[N],lmx[N],rmx[N],mx[N];

	void addNode(int pos,int &x,int f)
	{
		if(top2) x=stk[--top2];
		else x=++top1;

		LL(x)=RR(x)=0; pre[x]=f;
		flag[x]=flip[x]=0;
		valu[x]=a[pos];
		sum[x]=lmx[x]=rmx[x]=mx[x]=-INF;
	}
	inline void iswap(int x)
	{
		if(x==0)return;
		flip[x]^=1;
		swap(LL(x),RR(x));
		swap(lmx[x],rmx[x]);
	}
	inline void toSame(int x,int c)
	{
		if(x==0)return;
		flag[x]=1; 		same[x]=c;
		valu[x]=c;	sum[x]=sz[x]*c;
		lmx[x]=rmx[x]=mx[x]=max(sum[x],valu[x]);
	}
	void PushDown(int x)
	{
		if(x==0) return;
		if(flip[x])
		{
			iswap(LL(x)); iswap(RR(x));
			flip[x]=0;
		}
		if(flag[x])
		{
			toSame(LL(x),same[x]);
			toSame(RR(x),same[x]);
			flag[x]=0;
		}
	}
	void PushUp(int x)
	{
		sz[x]=1+sz[LL(x)]+sz[RR(x)];

		sum[x]=valu[x]+sum[LL(x)]+sum[RR(x)];
		mx[x]=lmx[x]=rmx[x]=-INF;

		lmx[x]=max(lmx[LL(x)],sum[LL(x)]+valu[x]+max(0,lmx[RR(x)]));
		rmx[x]=max(rmx[RR(x)],sum[RR(x)]+valu[x]+max(0,rmx[LL(x)]));

		mx[x]=max(mx[LL(x)],mx[RR(x)]);
		mx[x]=max(mx[x],valu[x]+max(0,rmx[LL(x)])+max(0,lmx[RR(x)]));
	}
	inline void Link(int x,int y,int f)
	{
		pre[x]=y; ch[y][f]=x;
	}
	inline void Rotate(int x,int f)//0表示左旋,1表示右旋
	{
		int y=pre[x],z=pre[y];

		PushDown(y); PushDown(x);

		Link(ch[x][f],y,!f);
		Link(x,z,RR(z)==y);
		Link(y,x,f);

		PushUp(y);
	}
	inline void Splay(int x,int goal)//将节点x旋转到goal下面
	{
		while(pre[x]!=goal)
		{
			int y=pre[x],z=pre[y];
			int cx=(LL(y)==x),cy=(LL(z)==y);

			if(z==goal) Rotate(x,cx);
			else
			{
				if(cx==cy) Rotate(y,cy);
				else Rotate(x,cx);
				Rotate(x,cy);
			}
		}
		PushUp(x);
		if(goal==0) Rt=x;
	}
	inline void Select(int K,int goal)//将第K个数旋转到goal下面。
	{
		int x=Rt;
		PushDown(x);
		while(sz[LL(x)]!=K)
		{
			if(K<sz[LL(x)]) x=ch[x][0];
			else K-=sz[LL(x)]+1,x=ch[x][1];
			PushDown(x);
		}
		//printf("find:%d\n",valu[x]);
		Splay(x,goal);
	}
	void Erase(int x)//删除以x为根的子树
	{
		int father=pre[x];
		int head=0,tail=0;
		for(que[tail++]=x;head<tail;head++)
		{
			stk[top2++]=que[head];
			if(LL(que[head])) que[tail++]=LL(que[head]);
			if(RR(que[head])) que[tail++]=RR(que[head]);
		}
		ch[father][ RR(father)==x ]=0;
	}
	void Insert()//插入一段区间
	{
		int pos,tot;
		scanf("%d%d",&pos,&tot);

		for(int i=1;i<=tot;i++) scanf("%d",&a[i]);

		Select(pos,0), Select(pos+1,Rt);
		build(1,tot,Kt,ch[Rt][1]);
		Splay(ch[Rt][1],0);
	}
	void Delete()//删除一段区间
	{
		int pos,tot;
		scanf("%d%d",&pos,&tot);

		Select(pos-1,0), Select(pos+tot,Rt);

		Erase(Kt);

		Splay(ch[Rt][1],0);
	}
	void MakeSame()//将一段区间内的值全部变成C
	{
		int pos,tot,c;
		scanf("%d%d%d",&pos,&tot,&c);

		Select(pos-1,0), Select(pos+tot,Rt);
		//debug();
		toSame(Kt,c);
		Splay(ch[Rt][1],0);
	}
	void Reverse()//将一段区间进行旋转
	{
		int pos,tot;
		scanf("%d%d",&pos,&tot);

		Select(pos-1,0), Select(pos+tot,Rt);
		iswap(Kt);
		Splay(ch[Rt][1],0);
	}
	void GetSum()//求一段区间和
	{
		int pos,tot;
		scanf("%d%d",&pos,&tot);

		Select(pos-1,0), Select(pos+tot,Rt);

		printf("%d\n",sum[Kt]);
	}
	void MaxSum()//求出子区间和的最大值。
	{
		Splay(1,0);
		Splay(2,1);
		printf("%d\n",mx[Kt]);
	}
	void build(int lft,int rht,int &x,int f)//建树
	{
		if(lft>rht) return;

		int mid=MID(lft,rht);
		addNode(mid,x,f);
		build(lft,mid-1,LL(x),x);
		build(mid+1,rht,RR(x),x);
		PushUp(x);
	}

	void init(int st,int ed)//初始化
	{
		Rt=top1=top2=0;
		sz[0]=pre[0]=LL(0)=RR(0)=0;

		lmx[0]=rmx[0]=mx[0]=-INF;

		addNode(0,Rt,0); addNode(0,RR(Rt),Rt);

		build(st,ed,Kt,RR(Rt));
		PushUp(RR(Rt)); PushUp(Rt);
	}
	void debug() { printf("Rt:%d\n",Rt); travel(Rt); }
	void travel(int x)
	{
		if(x==0) return;
		PushDown(x);
		travel(LL(x));
		printf("node:%d,pre:%d,lson:%d,rson:%d,valu:%d,mx:%d,lmx:%d,rmx:%d\n",
				x,pre[x],LL(x),RR(x),valu[x],mx[x],lmx[x],rmx[x]);
		travel(RR(x));
	}
}spt;
int main()
{
	int n,m;
	while(scanf("%d%d",&n,&m)!=EOF)
	{
		a[0]=-INF;
		for(int i=1;i<=n;i++) scanf("%d",&a[i]);

		spt.init(1,n);
		//spt.debug();

		char buf[100];
		for(int i=0;i<m;i++)
		{
			scanf("%s",buf);
			if(buf[0]=='I') spt.Insert();
			else if(buf[0]=='D') spt.Delete();
			else if(buf[0]=='M')
			{
				if(buf[2]=='K')spt.MakeSame();
				else spt.MaxSum();
			}
			else if(buf[0]=='R') spt.Reverse();
			else spt.GetSum();

			//spt.debug();
		}
	}
	return 0;
}






你可能感兴趣的:(Splay Tree(伸展树)[NOI2005]维修数列)