最近在研究平衡树,看起来这种东西又丧水又很深,感觉很难搞清楚。在Ditoly学长的建议下,我先学习了正常的treap,个人感觉这应该是平衡树当中比较好懂的而且比较好写的一种。
然而,发现带旋treap有很多无法支持的操作,例如各种区间操作,而且由于会旋转无法可持久化,这是一个十分影响实用性的问题,在没有办法支持区间操作的情况下,我有2种选择:
1)滚去学splay;2)学习无旋treap 正常人应该都会去学习splay,然而我选择了后者,因为貌似splay在FJ省选R2T1中被卡成傻逼了。。。。有个学长因此此题爆0。。。心疼。。。。。
所以准备先搞一搞无旋treap,再去搞splay。
**********************华丽的分割线***************************************************
以下是正文。
无旋treap,顾名思义就是没有了rotate的treap,而我们在无旋treap中十分重要的就是2个基础操作,可以说这2个操作才给我们带来了使用无旋treap的理由,它们就是merge(合并treap)和split(分离treap)。
你Insert要用到它们,Delete要用到它们,查询还是需要它们。。。。
这里首先强调一点,merge中合并的2棵treap是要求相对有序的,换而言之就是其中一棵treap的最右节点要严格不大于另一棵的最左节点,不然就和永无乡那题一样,你得写启发式合并暴力merge了,有兴趣的可以看一下那一篇的题解,然而和本题没有并没有毛线关系23333。
然后先看一道例题吧。。。这题在之前我写了一个标准的普通版treap,题解戳这里,不了解普通treap的和没看过原题的可以先看这一篇(当然还是建议在彻底理解的前提下阅读本文,而且本人语文感人,表达能力可能不佳,见谅,如果想要深入学习,建议看dalao的这篇)。
首先这是一道平衡树模板题,因为没有区间操作,所以你写普通treap是能过的。
接下来带有区间操作的模板题我另开一篇写了一道模板题,传送门戳我。
然后讲一下基本操作的实现:(不懂的可以最后结合本人代码理解:D)
I.merge(A,B)
A和B是2棵treap,实际上我们操作上是2个root。
首先我们默认max(A)是小于min(B)的。
然后当前的2棵treap的root节点,我们判断一下优先级,然后默认小的扔上面(维护treap性质),例如:
若A.pri<B.pri(即合并后A为B的祖先),我们显然A的左子树不需更改,而我们需要更改A的是右子树,因此令A的右儿子为merge(A.rightson,B)的root即可。
反之同理即上述内容的反演。
II.split(A,k)
split操作是分离A这棵treap中前K大的和后面的。
显然你只需要按照rank跑一遍找到分界点,然后根据每次是向左跑还是向右跑决定这个点属于哪一棵分出来的树:
1)向左跑说明这个点的权值较大,它以及它的右子树均属于第二棵treap;2)向右跑说明这个点及其左子树均被在前K个,属于第一棵treap。
因为这样的treap,没有了旋转操作,因此是可持久化的,这里改日再讲。
接下来回归模板题的基本操作。首先先默认权值相等的我们扔到右子树。
I)Insert 插入一个权值为val的节点。
我们只需要查找这个点在treap中的rank(记为k),然后split之后增加这个节点进行merge即可。
II)Delete 删去一个权值为val的节点。
我们只需要查找这个节点在treap中的rank,然后split分离出这个节点,然后对于剩下的2棵treap进行merge即可。
III)getkth 查找权值val在树上的最小排名。
由于我们正常查找返回的是最后的位置,所以查找(val-1)在树上的rank再+1即可。
IV)findkth 查找树上排名为rank的权值。
直接split分离这个点然后记录权值后再merge回去。
V)prefix 查找树上权值小于val的最大值。
直接findkth(getkth(val-1))即可。
VI)suffix查找树上权值大于val的最小值。
直接findkth(getkth(val)+1)即可。
接下来给出代码帮助大家理解,当然是跑得没有普通treap快的就是了。
#include <stdio.h> #include <algorithm> #define r register #define getchar() (S==TT&&(TT=(S=BB)+fread(BB,1,1<<15,stdin),TT==S)?EOF:*S++) char BB[1<<15],*S=BB,*TT=BB; inline int in(){ r int x=0;r bool f=0;r char c; for (;(c=getchar())<'0'||c>'9';) f=c=='-'; for (x=c-'0';(c=getchar())>='0'&&c<='9';) x=(x<<3)+(x<<1)+c-'0'; return f?-x:x; } namespace Treap{ inline int Rand(){ static int x=23333; return x^=x<<13,x^=x>>17,x^=x<<5; } struct node{ node *ls,*rs; int val,pri,sz; node(int val):val(val),pri(Rand()),ls(NULL),rs(NULL),sz(1){}; inline void combine(){sz=1+(ls?ls->sz:0)+(rs?rs->sz:0);} }*root; inline int Size(node *x){return x?x->sz:0;} node *merge(node *a,node *b){ if (!a) return b;if (!b) return a; if (a->pri<b->pri){ a->rs=merge(a->rs,b); a->combine(); return a; }else{ b->ls=merge(a,b->ls); b->combine(); return b; } } typedef std::pair<node*,node*> Droot; Droot split(node *x,int k){ if (!x) return Droot(NULL,NULL); r Droot y; if (Size(x->ls)>=k){ y=split(x->ls,k); x->ls=y.second; x->combine(); y.second=x; }else{ y=split(x->rs,k-Size(x->ls)-1); x->rs=y.first; x->combine(); y.first=x; }return y; } inline int findkth(int k){ r Droot x=split(root,k-1); r Droot y=split(x.second,1); r node *ans=y.first; root=merge(merge(x.first,ans),y.second); return ans->val; } int getkth(node *x,int val){ if (!x) return 0; return val<x->val?getkth(x->ls,val):getkth(x->rs,val)+Size(x->ls)+1; } inline void Insert(int val){ r int k=getkth(root,val); r Droot x=split(root,k); r node *a=new node(val); root=merge(merge(x.first,a),x.second); } inline void Delete(int val){ r int k=getkth(root,val); r Droot x=split(root,k-1); r Droot y=split(x.second,1); r node *ans=y.first; root=merge(x.first,y.second); delete ans; ans=NULL; } inline int prefix(int val){ r int k=getkth(root,val-1); return findkth(k); } inline int suffix(int val){ r int k=getkth(root,val); return findkth(k+1); } } void work(){ int q=in(); while(q--){ r int op=in(),x=in(); switch(op){ case 1:Treap::Insert(x);break; case 2:Treap::Delete(x);break; case 3:printf("%d ",Treap::getkth(Treap::root,x-1)+1);break; case 4:printf("%d ",Treap::findkth(x));break; case 5:printf("%d ",Treap::prefix(x));break; case 6:printf("%d ",Treap::suffix(x));break; } } } int main(){work();return 0;}