伸展树—系列题目

一、文艺二叉树(来源:codevs 3303)

有n个数,这个序列依次是(1,2,…,n-1,n),每次翻转区间(l,r),输出翻转的最终结果。


思路:

1、用树的话,如何做到区间反转?把需要反转的树放到一棵子树当中,令该子树的左右孩子对换即可;

2、很自然,会想到用layz的思路优化时间;

3、如果左右子树对调,这还是一棵“左<<右”的树吗?显然,它完全不合。这使得这棵树不能用原先的findip,因为它不符“左<<右”;所以,我们可以用findshuzi的思路来查询它的ip。线段树的操作splay不受影响。

4、此题建树时才用了建伸展树的方法,为的是让树一开始达到最平衡的情况。如果执意要用旧版,也没问题。建树还有一点需要注意,它多建了两棵树headtail,为的是能更方便操作。


总结:这题打破了以往对伸展树的定义,成了一棵没有大小之分的伸展树,它利用“伸展树无论怎样翻转,中序(前序、后序)遍历不变”的性质,用findshuzi()的思想来查询值为x的中序遍历位置。所以,即使是不能按大小顺序排列的数据,也可以考虑伸展树;换句话说,伸展树也可以记录没有大小之分的数据


代码:

#include
#include
#include
using namespace std;

int root;
int a[100010];
struct node
{
	int c,f,d,son[2];
	bool fz;
}tr[100010];int len=0;

void weihufz(int x)
{
	tr[x].fz=false;
	swap(tr[x].son[0],tr[x].son[1]);
	tr[tr[x].son[0]].fz^=1;
	tr[tr[x].son[1]].fz^=1;
}

void update(int x)
{
	tr[x].c=1+tr[tr[x].son[0]].c+tr[tr[x].son[1]].c;
}

void rotate(int x,int w)
{
	int f=tr[x].f;
	int ff=tr[f].f;
	int r,R;
	
	R=f;r=tr[x].son[w];
	tr[R].son[1-w]=r;
	if(r!=0) tr[r].f=R;
	
	R=ff;r=x;
	if(tr[ff].son[0]==f) tr[R].son[0]=r;
	else tr[R].son[1]=r;
	tr[r].f=R;
	
	R=x;r=f;
	tr[R].son[w]=r;
	tr[r].f=R;
	
	update(f);
	update(x);
}

void splay(int x,int rt)
{
	while(tr[x].f!=rt)
	{
		int f=tr[x].f;
		int ff=tr[f].f;
		if(rt==ff)
		{
			if(tr[x].fz==true) weihufz(x);
			if(tr[f].son[0]==x) rotate(x,1);
			else rotate(x,0);
		}
		else
		{
			if(tr[f].fz==true) weihufz(f);//父亲先翻转
			if(tr[x].fz==true) weihufz(x);//儿子再翻转
				 if(tr[ff].son[0]==f&&tr[f].son[0]==x){rotate(f,1);rotate(x,1);}
			else if(tr[ff].son[1]==f&&tr[f].son[1]==x){rotate(f,0);rotate(x,0);}
			else if(tr[ff].son[0]==f&&tr[f].son[1]==x){rotate(x,0);rotate(x,1);}
			else if(tr[ff].son[1]==f&&tr[f].son[0]==x){rotate(x,1);rotate(x,0);}
		}
	}
	if(rt==0) root=x;
}

int ins(int l,int r)
{
	if(l>r) return 0;
	len++;int now=len;
	int mid=(l+r)/2;
	int lc=ins(l,mid-1),rc=ins(mid+1,r);
	
	if(lc!=0) tr[lc].f=now;
	if(rc!=0) tr[rc].f=now;
	
	tr[now].d=mid;tr[now].fz=false;
	tr[now].c=tr[lc].c+tr[rc].c+1;
	tr[now].son[0]=lc;tr[now].son[1]=rc;
	return now;
}

int findip(int k)//findip 指的是找中序遍历第k的数的地址 
{
	int x=root;
	while(1)
	{
		if(tr[x].fz==true) weihufz(x);
		int lc=tr[x].son[0],rc=tr[x].son[1];
		if(k<=tr[lc].c) x=lc;
		else if(k>tr[lc].c+1){k-=tr[lc].c+1;x=rc;}
		else break;
	}
	return x;
}

void fanzhuan(int l,int r)
{
	int lc=findip(l-1),rc=findip(r+1);
	splay(lc,0);splay(rc,lc);
	int x=tr[rc].son[0];
	tr[x].fz^=1;
	
}

void dfs(int x)//中序遍历
{
	if(tr[x].fz==true) weihufz(x);
	int lc=tr[x].son[0],rc=tr[x].son[1];
	if(lc!=0) dfs(lc);//左
	a[++len]=tr[x].d;//根
	if(rc!=0) dfs(rc);//右
}

int main()
{
	
	int n,m;
	scanf("%d%d",&n,&m);
	root=ins(0,n+1);
	while(m--)
	{
		int l,r;
		scanf("%d%d",&l,&r);
		l++;r++;//+1是因为受到值为0的节点的影响
		fanzhuan(l,r);
	}
	len=0;
	dfs(root);
	for(int i=2;i



二、二逼平衡树(来源:bzoj 3196)

有n个数,需要提供以下几种操作:

1.查询k在区间内的排名

2.查询区间内排名为k的值

3.修改pos位置上的数值为k

4.查询k在区间内的前驱(前驱定义为小于x,且最大的数)

5.查询k在区间内的后继(后继定义为大于x,且最小的数)


思路:

1、如果用普通伸展树,那么会受到大小不是有序的问题的影响,1、2、4、5都很难完成;

2、于是,我们用一种更加高级的算法“树套树”来解决。这题我们用线段树伸展树,其中线段树的作用是掌控伸展树,伸展树都是具有“左<根<右”的树,用于具体的查询。用通俗点的话来讲,这题中的每段线段树都吊着一颗它管辖范围内的伸展树;


3、如何搭建起一棵线段树+__棵伸展树?

   (1)先把线段树的框架搭好:对于每棵线段树而言lc=now*2,rc=now*2+1,管理范围(父亲的 l ,mid)或(mid+1,父亲的 r );

   (2)每次新加入1个值,就往线段树里对应的位置塞,当然每段线段树它记录的是一棵伸展树的根,所以新加的值要加入(Splay_ins)这棵伸展树中去;

   (3)这样,一棵+__棵的大树,就长好了。现在区间为(l,r)的线段树中,就有一棵有大小之分的专管l-r的伸展树了。


4、有了这样庞大的数据结构,我们就可以开始装逼了:

   1.在包含k且不重叠的伸展树中查找统计小于k的数的个数,+1就是k的排名;

   2.这是本题的难点。通过二分 0~ma 的排名,尝试是否合理;

   3.凡是包含pos的伸展树全部删去原来的值,在插入一个值为k的值;

   4.在包含k且不重叠的伸展树中重复查找前驱,取最大的前驱为k的前驱;

   5.在包含k且不重叠的伸展树中重复查找后继,取最小的后继为k的后继。


总结:如果一堆没有大小之分的数据偏要进行数据大小的处理,那么线段树伸展树乃是首选。有了这样的基础后,伸展树的插入、查找和删除操作都建立在线段树的特定区间上,也就是说,线段树管理范围,伸展树才是真正的主人,发挥着查询、更改的重要作用


代码:(以Splay开头的是伸展树的操作,以SegTree开头的是线段树的操作

#include
#include
#include
using namespace std;

int ans;
int a[50010],root[4000005];//root[i]线段树编号为i的,所管理的伸展树的根
struct node
{
	int f,d,c,son[2],n;
}tr[4000005];int len=0;

void Splay_add(int d,int f)
{
	len++;
	tr[len].d=d;tr[len].f=f;tr[len].c=tr[len].n=1;
	tr[len].son[0]=tr[len].son[1]=0;
	if(d1){tr[x].n--;Splay_update(x);}
	else if(tr[x].son[0]==0&&tr[x].son[1]==0){root[now]=0;}
	else if(tr[x].son[0]==0&&tr[x].son[1]!=0){root[now]=tr[x].son[1];tr[root[now]].f=0;}
	else if(tr[x].son[0]!=0&&tr[x].son[1]==0){root[now]=tr[x].son[0];tr[root[now]].f=0;}
	else
	{
		int p=tr[x].son[0];
		while(tr[p].son[1]!=0) p=tr[p].son[1];
		Splay_splay(now,p,x);
		
		int R=p,r=tr[x].son[1];
		tr[R].son[1]=r;
		tr[r].f=R;
		
		root[now]=R;tr[root[now]].f=0;
		Splay_update(R);
	}
}

int Splay_findqianqu(int now,int d)//x=d&&tr[x].son[0]!=0)
	{
		x=tr[x].son[0];
		while(tr[x].son[1]!=0) x=tr[x].son[1];
	}
	else if(tr[x].d>=d) return -999999999;
	return tr[x].d;
}

int Splay_findhouji(int now,int d)//x>d
{
	int x=Splay_findip(now,d);Splay_splay(now,x,0);
	if(tr[x].d<=d&&tr[x].son[1]!=0)
	{
		x=tr[x].son[1];
		while(tr[x].son[0]!=0) x=tr[x].son[0];
	}
	else if(tr[x].d<=d) return 999999999;
	return tr[x].d;
}

void SegTree_ins(int now,int l,int r,int x,int d)//在区间内插入值d 
{
	int mid=(l+r)/2;
	Splay_ins(now,d);//该区间的伸展树插入值d 
	if(l==r) return ;
	else if(x<=mid) SegTree_ins(now<<1,l,mid,x,d);
	else SegTree_ins(now<<1|1,mid+1,r,x,d);
}

void SegTree_askrank(int now,int l,int r,int al,int ar,int d)//查询区间(al,ar)中小于值d的数的个数 
{
	int mid=(l+r)/2;
	if(al<=l&&r<=ar)//若该线段树(l,r)在查询范围内
	{
		ans+=Splay_findrank(now,d);
		return ;
	}
	if(al<=mid) SegTree_askrank(now<<1,l,mid,al,ar,d);
	if(mid+1<=ar) SegTree_askrank(now<<1|1,mid+1,r,al,ar,d);
}

void SegTree_change(int now,int l,int r,int pos,int d)
{
	int mid=(l+r)/2;
	Splay_del(now,a[pos]);
	Splay_ins(now,d);
	if(l==r) return ;
	if(pos<=mid) SegTree_change(now<<1,l,mid,pos,d);
	else SegTree_change(now<<1|1,mid+1,r,pos,d);
}

void SegTree_askqianqu(int now,int l,int r,int al,int ar,int d)
{
	int mid=(l+r)/2;
	if(al<=l&&r<=ar)
	{
		ans=max(ans,Splay_findqianqu(now,d));
		return ;
	}
	if(al<=mid) SegTree_askqianqu(now<<1,l,mid,al,ar,d);
	if(mid+1<=ar) SegTree_askqianqu(now<<1|1,mid+1,r,al,ar,d);
}

void SegTree_askhouji(int now,int l,int r,int al,int ar,int d)
{
	int mid=(l+r)/2;
	if(al<=l&&r<=ar)
	{
		ans=min(ans,Splay_findhouji(now,d));
		return ;
	}
	if(al<=mid) SegTree_askhouji(now<<1,l,mid,al,ar,d);
	if(mid+1<=ar) SegTree_askhouji(now<<1|1,mid+1,r,al,ar,d);
}

int main()
{
	int n,m,ma=-999999999;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		ma=max(ma,a[i]);
		SegTree_ins(1,1,n,i,a[i]);
	}
	while(m--)
	{
		int opt,l,r,pos,k;
		scanf("%d",&opt);
		switch(opt)
		{
			case 1:
			{
				scanf("%d%d%d",&l,&r,&k);
				ans=0;
                SegTree_askrank(1,1,n,l,r,k);
                printf("%d\n",ans+1);
				break;
			}
			case 2:
			{ 
				scanf("%d%d%d",&l,&r,&k);
				int head=0,tail=ma,Ans;
				while(head<=tail)
				{
					int mid=(head+tail)/2;
					ans=1;
					SegTree_askrank(1,1,n,l,r,mid);
					if(ans<=k)
					{
						Ans=head;
						head=mid+1;
					}
					else tail=mid-1;
				}
				printf("%d\n",Ans);
				break;
			}
			case 3:
			{
				scanf("%d%d",&pos,&k);
				SegTree_change(1,1,n,pos,k);
				a[pos]=k;
				ma=max(ma,k);
				break;
			}
			case 4:
			{
				scanf("%d%d%d",&l,&r,&k);
				ans=0;
				SegTree_askqianqu(1,1,n,l,r,k);
				printf("%d\n",ans);
				break;
			}
			case 5:
			{
				scanf("%d%d%d",&l,&r,&k);
				ans=999999999;
				SegTree_askhouji(1,1,n,l,r,k);
				printf("%d\n",ans);
				break;
			}
		}
	}
}

做完这两道伸展树的题后,我不禁感叹:伸展树真的太神奇了!



推荐:《伸展树—作用介绍》http://blog.csdn.net/a_bright_ch/article/details/72795172

你可能感兴趣的:(伸展树)