最近在研究平衡樹,看起來這種東西又喪水又很深,感覺很難搞清楚。在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\n",Treap::getkth(Treap::root,x-1)+1);break; case 4:printf("%d\n",Treap::findkth(x));break; case 5:printf("%d\n",Treap::prefix(x));break; case 6:printf("%d\n",Treap::suffix(x));break; } } } int main(){work();return 0;}