伸展树—系列题目

来源:互联网 发布:数控平面钻的编程 编辑:程序博客网 时间:2024/06/02 05:36

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

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


思路:

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

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

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

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


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


代码:

#include<cstdio>#include<cstring>#include<algorithm>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<len;i++) printf("%d ",a[i])//i=1是head i=len是tailreturn 0;}



二、二逼平衡树(来源: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<cstdio>#include<cstring>#include<algorithm>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(d<tr[f].d) tr[f].son[0]=len;else tr[f].son[1]=len;}int Splay_findip(int now,int d){int x=root[now];while(tr[x].d!=d){if(d<tr[x].d){if(tr[x].son[0]==0) break;x=tr[x].son[0];}else{if(tr[x].son[1]==0) break;x=tr[x].son[1];}}return x;}void Splay_update(int x){tr[x].c=tr[x].n+tr[tr[x].son[0]].c+tr[tr[x].son[1]].c;}void Splay_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;Splay_update(f);Splay_update(x);}void Splay_splay(int now,int x,int rt){while(tr[x].f!=rt){int f=tr[x].f;int ff=tr[f].f;if(rt==ff){if(tr[f].son[0]==x) Splay_rotate(x,1);else Splay_rotate(x,0);}else{ if(tr[ff].son[0]==f&&tr[f].son[0]==x){Splay_rotate(f,1);Splay_rotate(x,1);}else if(tr[ff].son[1]==f&&tr[f].son[1]==x){Splay_rotate(f,0);Splay_rotate(x,0);}else if(tr[ff].son[0]==f&&tr[f].son[1]==x){Splay_rotate(x,0);Splay_rotate(x,1);}else if(tr[ff].son[1]==f&&tr[f].son[0]==x){Splay_rotate(x,1);Splay_rotate(x,0);}}}if(rt==0) root[now]=x;}void Splay_ins(int now,int d)//在编号为now的伸展树下,插入值d {if(root[now]==0){Splay_add(d,0);root[now]=len;return ;}int x=Splay_findip(now,d);if(tr[x].d==d){tr[x].n++;Splay_update(x);Splay_splay(now,x,0);}else{Splay_add(d,x);Splay_update(x);Splay_splay(now,len,0);}}int Splay_findrank(int now,int d)//在编号为now的伸展树下,查询小于值d的数的个数 {int x=Splay_findip(now,d);Splay_splay(now,x,0);if(tr[x].d<d) return tr[tr[x].son[0]].c+tr[x].n;else return tr[tr[x].son[0]].c;}void Splay_del(int now,int d){int x=Splay_findip(now,d);Splay_splay(now,x,0);if(tr[x].n>1){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{int x=Splay_findip(now,d);Splay_splay(now,x,0);if(tr[x].d>=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

原创粉丝点击