FHQ Treap摘要

原理

以隨機數維護平衡,使樹高指望爲logn級別c++

不依靠旋轉,只有兩個核心操做merge(合併)和split(拆分)git

所以可持久化ide

先介紹變量

 1 const int N=100005;
 2 int n;
 3 struct Node {
 4     int val,key,siz;                                                                //權值,隨機權值,子樹大小 
 5     int son[2];                                                                        //左右兒子(0左1右) 
 6     void res() {                                                                    //清空該節點(用於刪除) 
 7         son[0]=son[1]=siz=val=key=0;
 8     }
 9 } tree[N];
10 int ins;
11 int mem[N],inm;                                                                    //內存回收池 
12 int root;
var

核心操做

merge並返回合併後的根

假設有兩顆子樹x,y,且x的全部節點的值都小於y的全部節點的值,隨機權值都以小根堆的形式存儲。ui

此時要合併x和y。咱們先比較它們的根的隨機權值,發現1<3,則x的左子樹所有不變,讓右子樹繼續和y合併。spa

這時咱們發現,5>3,因此y做爲rot的右兒子,y的右子樹所有不變,讓y的左子樹繼續和x合併。3d

因爲5>4,因此y和y的右子樹做爲rot的左兒子,y的左子樹繼續和x合併。code

5<7,因此接入x和它的左子樹做爲rot的左兒子。blog

發現此時x爲0,因此直接返回y,合併結束。內存

 1 int merge(int x,int y) {                                                            //合併兩棵樹 
 2     if(!x||!y) return x+y;                                                            //如有一棵樹爲0則說明該樹爲空或已合併完成 
 3     if(tree[x].key<tree[y].key) {                                                    //若x的隨機權值大於y的 
 4         tree[x].son[1]=merge(tree[x].son[1],y);                                        //x的右子樹和y合併,返回的根做爲x的右子樹 
 5  update(x);  6         return x;                                                                    //返回x 
 7     } else {  8         tree[y].son[0]=merge(x,tree[y].son[0]);                                        //不然y的左子樹和x合併,返回的根做爲y的左子樹
 9  update(y); 10         return y;                                                                    //返回y 
11  } 12 }
merge

split拆分一棵樹

split有兩種拆分方式,按權值拆或按排名拆。get

按權值split

首先得有個基準a,即小於等於a的節點所有進入左樹,大於a的節點所有進入右樹。這裏以a=25爲例。

首先,發現rot的權值=15<25,由平衡樹的性質可知,rot的左子樹全部節點權值必定小於25,因此rot和它的的左子樹所有進入左樹,繼續拆分rot的右子樹。

32>25,因此rot和它的右子樹所有進入右樹,繼續拆分rot的左子樹。

29>25,同上。

24<25,因此拆分右子樹。

27>25,因此拆分左子樹。

發現此時rot爲0,因此拆分完畢,返回。

 1 void split1(int now,int k,int &x,int &y) {                                            //按權值拆分兩顆子樹(注意要用引用) 
 2     if(!now) {                                                                        //子樹爲0,說明無需拆分或拆分完畢,返回 
 3         x=y=0;  4         return;  5  }  6     if(tree[now].val<=k) {                                                            //若權值小於等於k 
 7         x=now;  8         split1(tree[now].son[1],k,tree[now].son[1],y);                                //拆進左樹並拆分右子樹 
 9     } else { 10         y=now; 11         split1(tree[now].son[0],k,x,tree[now].son[0]);                                //不然拆進右樹並拆分左子樹 
12  } 13  update(now); 14 }
split1

按排名split

就是把前k個節點拆入左樹,其它節點拆入右樹。這裏以k=5爲例。

rot的左子樹的siz+1=3<5,因此rot和它的左子樹進入左樹,其餘節點拆分5-3=2個節點進入左樹。

4+1>2,因此rot和右子樹進入右樹,其它節點繼續拆分出2個節點進入左樹。

3+1>2,同上。

1+1=2,因此rot和左子樹進入左樹,其它節點繼續拆分2-2=0個節點進入左樹。

1+0>0,因此rot和右子樹進入右樹,其它節點繼續拆分0個節點進入左樹。

rot爲0,拆分結束。

 1 void split2(int now,int k,int &x,int &y) {                                            //按權值拆分兩顆子樹(一樣要用引用)
 2     if(!now) {                                                                        //子樹爲0,說明無需拆分或拆分完畢,返回 
 3         x=y=0;  4         return;  5  }  6  update(now);  7     if(k>tree[tree[now].son[0]].siz) {                                                //若作子樹大小+1小於等於k 
 8         x=now;  9         split2(tree[now].son[1],k-tree[tree[now].son[0]].siz-1,tree[now].son[1],y);//拆進左樹並拆分右子樹(注意右子樹分配的名額要減小) 
10     } else { 11         y=now; 12         split2(tree[now].son[0],k,x,tree[now].son[0]);                                //不然拆進右樹並拆分左子樹 
13  } 14  update(now); 15 }
split2

其餘操做

會了merge和split,其餘操做就是瞎搞。

插入

插入x,先新建節點,再以x爲界按權值split整棵樹爲a,b,再按順序merge a,x,b。

1 void insert(int x) { 2     int u=(inm?mem[inm--]:++ins);                                                    //新建節點 
3     tree[u].key=rand(); 4     tree[u].val=x; 5     tree[u].siz=1; 6     int a,b; 7     split1(root,x,a,b);                                                                //split 
8     root=merge(merge(a,u),b);                                                        //merge 
9 }
insert

刪除

要刪除x,先將整棵樹以x按權值split成a和b,再將a以x-1按權值split成c和d,則d中節點權值全爲x。在d中split出排名爲1的節點e和其它節點f,則e爲要刪的點。最後merge c,f,b。

1 void delet(int x) { 2     int a,b,c,d,e,f; 3     split1(root,x,a,b);                                                                //split整棵樹 
4     split1(a,x-1,c,d);                                                                //將a split爲c和d 
5     split2(d,1,e,f);                                                                //將d split爲e和f,則e爲咱們要刪的節點 
6     mem[++inm]=e;                                                                    //回收 
7     tree[e].res();                                                                    //重置 
8     root=merge(merge(c,f),b);                                                        //merge
9 }
delet

查詢x的排名

先將整棵樹以x-1按權值split成a和b,則a的siz+1即爲x的排名。

1 int finrnk(int x) { 2     int a,b,c; 3     split1(root,x-1,a,b);                                                            //split整棵樹 
4     c=tree[a].siz+1;                                                                //a的大小就是小於x的數的個數 
5     root=merge(a,b);                                                                //merge 
6     return c; 7 }
finrnk

查詢第x小值

先split出整棵樹前x-1小節點,則右樹最小節點即爲所求節點,再次split便可。

1 int finnum(int &rot,int x) { 2     int a,b,c,d,e; 3     split2(rot,x-1,a,b);                                                            //split這棵樹 
4     split2(b,1,c,d);                                                                //split出b中第1個節點 
5     e=tree[c].val;                                                                    //c即爲第x小節點 
6     rot=merge(a,merge(c,d));                                                        //merge 
7     return e; 8 }
finnum

查x前驅

將整棵樹以x-1按權值split,左樹中最大節點即爲所求節點,轉入第x小值問題。

1 int las(int x) { 2     int a,b,c; 3     split1(root,x-1,a,b);                                                            //split整棵樹 
4     c=finnum(a,tree[a].siz);                                                        //找左樹最大值 
5     root=merge(a,b);                                                                //merge 
6     return c; 7 }
las

查x後繼

將整棵樹以x按權值split,右樹中最小節點即爲所求節點,轉入第x小值問題。

1 int nex(int x) { 2     int a,b,c; 3     split1(root,x,a,b);                                                                //split整棵樹 
4     c=finnum(b,1);                                                                    //找右樹最小值 
5     root=merge(a,b);                                                                //merge 
6     return c; 7 }
nex

時空複雜度

時間複雜度

merge、split:指望樹高爲logn,所以複雜度爲指望O(logn)

插入、刪除、查詢:基於以上兩種操做,複雜度指望O(logn)

常數比Treap大,但比splay小的多

空間複雜度

O(n)

例題

洛谷P3369【模板】普通平衡樹

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define INF 0x7fffffff
 4 #define ME 0x7f
 5 #define FO(s) freopen(s".in","r",stdin);freopen(s".out","w",stdout)
 6 #define fui(i,a,b,c) for(int i=(a);i<=(b);i+=(c))
 7 #define fdi(i,a,b,c) for(int i=(a);i>=(b);i-=(c))
 8 #define fel(i,a) for(register int i=h[a];i;i=ne[i])
 9 #define ll long long
10 #define MEM(a,b) memset(a,b,sizeof(a))
11 #define maxn (100000+10)
12 int n;
13 struct Node{int val,key,siz;int son[2];void res(){son[0]=son[1]=siz=val=key=0;}}tree[maxn];
14 int ins,mem[maxn],inm,root;
15 template<class T>
16 inline T read(T &n){
17     n=0;int t=1;double x=10;char ch;
18     for(ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());(ch=='-')?t=-1:n=ch-'0';
19     for(ch=getchar();isdigit(ch);ch=getchar()) n=n*10+ch-'0';
20     if(ch=='.') for(ch=getchar();isdigit(ch);ch=getchar()) n+=(ch-'0')/x,x*=10;
21     return (n*=t);
22 }void update(int x){tree[x].siz=tree[tree[x].son[0]].siz+tree[tree[x].son[1]].siz+1;}int merge(int x,int y){if(!x||!y) return x+y;
23     if(tree[x].key<tree[y].key){tree[x].son[1]=merge(tree[x].son[1],y);update(x);return x;}
24     else{tree[y].son[0]=merge(x,tree[y].son[0]);update(y);return y;}
25 }void split1(int now,int k,int &x,int &y){if(!now){x=y=0;return;}
26     if(tree[now].val<=k){x=now;split1(tree[now].son[1],k,tree[now].son[1],y);}
27     else{y=now;split1(tree[now].son[0],k,x,tree[now].son[0]);}update(now);
28 }void split2(int now,int k,int &x,int &y){if(!now){x=y=0;return;}update(now);
29     if(k>tree[tree[now].son[0]].siz){x=now;
30     split2(tree[now].son[1],k-tree[tree[now].son[0]].siz-1,tree[now].son[1],y);}
31     else{y=now;split2(tree[now].son[0],k,x,tree[now].son[0]);}update(now);
32 }void insert(int x){int u=(inm?mem[inm--]:++ins);
33     tree[u].key=rand();tree[u].val=x;tree[u].siz=1;
34     int a,b;split1(root,x,a,b);root=merge(merge(a,u),b);
35 }void delet(int x){int a,b,c,d,e,f;
36     split1(root,x,a,b);split1(a,x-1,c,d);split2(d,1,e,f);
37     mem[++inm]=e;tree[e].res();root=merge(merge(c,f),b);
38 }int finrnk(int x){int a,b,c;split1(root,x-1,a,b);c=tree[a].siz+1;root=merge(a,b);return c;}
39 int finnum(int &rot,int x){int a,b,c,d,e;split2(rot,x-1,a,b);
40     split2(b,1,c,d);e=tree[c].val;rot=merge(a,merge(c,d));return e;
41 }int las(int x){int a,b,c;split1(root,x-1,a,b);c=finnum(a,tree[a].siz);root=merge(a,b);return c;}
42 int nex(int x){int a,b,c;split1(root,x,a,b);c=finnum(b,1);root=merge(a,b);return c;}
43 int main(){
44     read(n);
45     fui(i,1,n,1){
46         int opt,x;read(opt);read(x);
47         switch(opt){
48             case 1:insert(x);break;
49             case 2:delet(x);break;
50             case 3:cout<<finrnk(x)<<endl;break;
51             case 4:cout<<finnum(root,x)<<endl;break;
52             case 5:cout<<las(x)<<endl;break;
53             case 6:cout<<nex(x)<<endl;break;
54         }
55     }
56     return 0;
57 }
AC代碼

FHQ Treap的其餘做用

最重要的一點是它能夠代替區間操做!並且支持可持久化!!!

區間操做

將每一個點按它們的下標做爲關鍵字,其餘的像普通FHQ Treap就好了。

區間翻轉的話,每次merge和split都pushdown一下。

洛谷【模板】文藝平衡樹(Splay)

 1 #include<bits/stdc++.h>
 2 using namespace std;  3 #define INF 0x7fffffff
 4 #define ME 0x7f
 5 #define FO(s) freopen(s".in","r",stdin);freopen(s".out","w",stdout)
 6 #define fui(i,a,b,c) for(int i=(a);i<=(b);i+=(c))
 7 #define fdi(i,a,b,c) for(int i=(a);i>=(b);i-=(c))
 8 #define fel(i,a) for(register int i=h[a];i;i=ne[i])
 9 #define ll long long
10 #define MEM(a,b) memset(a,b,sizeof(a))
11 #define maxn (100000+10)
12 int n,m; 13 struct Node{ 14     int key,val; 15     int siz,son[2]; 16     char iz; 17     Node(){key=val=siz=son[0]=son[1]=iz=0;} 18     Node(int x,int y){key=x,val=y,siz=1,son[0]=son[1]=iz=0;} 19 }tree[maxn]; 20 int root; 21 int l,r; 22 int rnd(){static int seed=703;return seed=int(seed*48271LL%(~0u>>1));} 23 template<class T>
24 inline T read(T &n){ 25     n=0;int t=1;double x=10;char ch; 26     for(ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());(ch=='-')?t=-1:n=ch-'0'; 27     for(ch=getchar();isdigit(ch);ch=getchar()) n=n*10+ch-'0'; 28     if(ch=='.') for(ch=getchar();isdigit(ch);ch=getchar()) n+=(ch-'0')/x,x*=10; 29     return (n*=t); 30 } 31 void update(int x){tree[x].siz=tree[tree[x].son[0]].siz+tree[tree[x].son[1]].siz+1;} 32 void pushdown(int x){ 33     if(tree[x].iz){ 34         tree[x].iz=0;swap(tree[x].son[0],tree[x].son[1]); 35         tree[tree[x].son[0]].iz^=1;tree[tree[x].son[1]].iz^=1; 36  } 37 } 38 int merge(int x,int y){ 39     if(!x||!y) return x+y;pushdown(x);pushdown(y); 40     if(tree[x].key<tree[y].key){tree[x].son[1]=merge(tree[x].son[1],y);update(x);return x;} 41     else{tree[y].son[0]=merge(x,tree[y].son[0]);update(y);return y;} 42 } 43 void split(int now,int k,int &x,int &y){ 44     if(!now){x=y=0;return;}pushdown(now); 45     if(tree[tree[now].son[0]].siz>=k){y=now;split(tree[now].son[0],k,x,tree[now].son[0]);} 46     else{x=now;split(tree[now].son[1],k-tree[tree[now].son[0]].siz-1,tree[now].son[1],y);} 47  update(now); 48 } 49 void dfs(int now){ 50  pushdown(now); 51     if(tree[now].son[0]) dfs(tree[now].son[0]); 52     printf("%d ",tree[now].val); 53     if(tree[now].son[1]) dfs(tree[now].son[1]); 54 } 55 int main(){ 56  read(n);read(m); 57     fui(i,1,n,1){tree[i]=(Node){rnd(),i};root=merge(root,i);} 58     fui(i,1,m,1){ 59         read(l);read(r);int a,b,c; 60         split(root,r,a,c);split(a,l-1,a,b); 61         tree[b].iz^=1; 62         root=merge(merge(a,b),c); 63  } 64  dfs(root); 65     return 0; 66 }
AC代碼

可持久化

還沒折騰出來。。。最近也沒時間折騰了。。。來日再說吧。。。

相關文章
相關標籤/搜索