首先說一下,
這個東西可以搞一切bst,treap,splay所能搞的東西
pre
今天心血來潮,
想搞一搞平衡樹,
先百度了一下平衡樹,發現正宗的平衡樹寫法應該是在二叉查詢樹的基礎上加什麼左左左右右左右右的旋轉之類的,
思路比較好理解,但是
程式碼量。。。。。。。。
一看就頭大,,
然後,在洛谷翻題解的時候無意間看到了遠航之曲發的一篇非常短小精悍的題解,
於是就學了一下
FHQ Treap
這個東西的學名應該是叫做fhq treap,應該是treap的強化版。
整個資料結構中只有兩個操作:
1.分離(split) 就是把一棵樹分成兩個樹
2.合併(merge)把兩棵樹合成一棵樹
對於FHQ 的兩種操作的原理以及實現,
我在這裡就不去贅述,
大家可以去看一下遠航之曲寫的部落格
http://www.yhzq-blog.cc/fhq-treap%e6%80%bb%e7%bb%93/
在這裡我主要是在講解一下程式碼的具體實現(當然也有可能不對。。)
CODE
題目連結:
https://www.luogu.org/problem/show?pid=3369
先說一下各個陣列的含義:
1 int ch[MAXN][3];// 0左孩子 1右孩子 2 int val[MAXN];// 每一個點的權值 3 int pri[MAXN];// 隨機生成的附件權值 4 int siz[MAXN];// 以i為節點的樹的節點數量 5 int sz;// 總結點的數量
然後來分別說明一下六中操作的實現
1.插入:
split(root,a,x,y);
root=merge(merge(x,new_node(a)),y);
這個比較好理解,我們先把樹分為x,y兩部分,然後把新的節點a看做是一棵樹,先與x合併,合併完之後將合併的整體與y合併
2.刪除
1 split(root,a,x,z); 2 split(x,a-1,x,y); 3 y=merge(ch[y][0],ch[y][1]); 4 root=merge(merge(x,y),z);
首先我們把樹分為x和z兩部分
那麼x樹中的最大權值為a
再把x分為x和y兩部分。
此時x中的最大權值為a-1,且權值為a的節點一定是y的根節點。
然後我們可以無視y的根節點,直接把y的左右孩子合併起來,這樣就成功的刪除了根節點,
最後再把x,y,z合併起來就好
3.查詢a的排名
1 split(root,a-1,x,y); 2 printf("%d\n",siz[x]+1); 3 root=merge(x,y);
我們首先按照a-1的權值把樹分開。
那麼x樹中最大的應該是a-1。
那麼a的排名就是siz[x]+1
4.查詢排名為a的數
1 printf("%d\n",val[kth(root,a)]);
直接呼叫查詢排名的函式即可,
這個函式應該比較好理解。。
5.求x的前驅(前驅定義為小於a,且最大的數)
1 split(root,a-1,x,y); 2 printf("%d\n",val[kth(x,siz[x])]); 3 root=merge(x,y);
因為要小於a,那麼我們按照a-1的權值劃分,
x中最大的一定是<=a-1的,
所以我們直接輸出x中最大的數就好,
(這裡有一個小技巧,因為siz儲存的是節點的數目,然後根據二叉查詢樹的性質,編號最大的就是值最大的)
6.求x的後繼(後繼定義為大於x,且最小的數)
1 split(root,a,x,y); 2 printf("%d\n",val[kth(y,1)]); 3 root=merge(x,y);
和上面的原理類似,
留給大家思考,
不懂的再問我。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 #include<algorithm> 6 #include<cstdlib> 7 #include<ctime> 8 using namespace std; 9 const int MAXN=100001; 10 static void read(int &n) 11 { 12 char c='+';int x=0;bool flag=0; 13 while(c<'0'||c>'9'){c=getchar();if(c=='-')flag=1;} 14 while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c-48);c=getchar();} 15 flag==1?n=-x:n=x; 16 } 17 int ch[MAXN][3];// 0左孩子 1右孩子 18 int val[MAXN];// 每一個點的權值 19 int pri[MAXN];// 隨機生成的附件權值 20 int siz[MAXN];// 以i為節點的樹的節點數量 21 int sz;// 總結點的數量 22 void update(int x) 23 { 24 siz[x]=1+siz[ch[x][0]]+siz[ch[x][1]]; 25 } 26 int new_node(int v) 27 { 28 siz[++sz]=1;// 新開闢一個節點 29 val[sz]=v; 30 pri[sz]=rand(); 31 return sz; 32 } 33 int merge(int x,int y)// 合併 34 { 35 if(!x||!y) return x+y;// x和y中必定有一個是0 36 if(pri[x]<pri[y])// 把x加到左邊的樹上 37 { 38 ch[x][1]=merge(ch[x][1],y);// 不懂的看GIF圖 39 update(x); 40 return x; 41 } 42 else 43 { 44 ch[y][0]=merge(x,ch[y][0]); 45 update(y); 46 return y; 47 } 48 } 49 void split(int now,int k,int &x,int &y) 50 { 51 if(!now) x=y=0;// 到達葉子節點 52 else 53 { 54 if(val[now]<=k)// 分離右子樹 55 x=now,split(ch[now][1],k,ch[now][1],y); 56 else 57 y=now,split(ch[now][0],k,x,ch[now][0]); 58 update(now); 59 } 60 } 61 int kth(int now,int k)// 查詢排名 62 { 63 while(1) 64 { 65 if(k<=siz[ch[now][0]]) 66 now=ch[now][0];// 在左子樹中,且數量小於左子樹的大小,迭代尋找 67 else if(k==siz[ch[now][0]]+1) 68 return now;// 找到了 69 else 70 k-=siz[ch[now][0]]+1,now=ch[now][1];// 去右子樹找 71 } 72 } 73 int main() 74 { 75 srand((unsigned)time(NULL)); 76 int n; 77 read(n); 78 int root=0,x,y,z; 79 for(int i=1;i<=n;i++) 80 { 81 int how,a; 82 read(how);read(a); 83 if(how==1)// 插入 84 { 85 split(root,a,x,y); 86 root=merge(merge(x,new_node(a)),y); 87 } 88 else if(how==2)//刪除x 89 { 90 split(root,a,x,z); 91 split(x,a-1,x,y); 92 y=merge(ch[y][0],ch[y][1]); 93 root=merge(merge(x,y),z); 94 } 95 else if(how==3)//查詢x的排名 96 { 97 split(root,a-1,x,y); 98 printf("%d\n",siz[x]+1); 99 root=merge(x,y); 100 } 101 else if(how==4)// 查詢排名為x的數 102 { 103 printf("%d\n",val[kth(root,a)]); 104 } 105 else if(how==5)// 求x的前驅 106 { 107 split(root,a-1,x,y); 108 printf("%d\n",val[kth(x,siz[x])]); 109 root=merge(x,y); 110 } 111 else if(how==6)// 求x的後繼 112 { 113 split(root,a,x,y); 114 printf("%d\n",val[kth(y,1)]); 115 root=merge(x,y); 116 } 117 } 118 return 0; 119 }
最後說一下,FHQ其實是可以處理區間問題的,
主要就是先把r+1的拆出來,然後把l的拆出來。
但是有些細節問題特別神奇,至今沒有搞懂。
如果你會的話希望你能給本蒟蒻講一下。
謝謝