非旋 treap 結構體數組版(無指針)詳解,有圖有真相

非旋  $treap$ (FHQ treap)的簡單入門html

 

前置技能

建議在掌握普通 treap 以及 左偏堆(也就是可並堆)食用本blognode

原理

以隨機數維護平衡,使樹高指望爲logn級別, FHQ 不依靠旋轉,只有兩個核心操做merge(合併)和split(拆分)ios

所謂隨機數維護平衡就是給每一個節點一個隨機值 key (下文中沒有加隨機的就表明是真實權值),git

而後整棵樹中 key 值要知足小(大)根堆的性質(也就是heap),ide

同時也要知足平衡樹(tree)的性質(也就是每一個節點左子樹內節點真實權值小於它,右子樹相反)函數

而後這個玩意兒就有了一個草率的名字:treap (tree 和 heap 的結合體)spa

結構體變量介紹

 

 1 int Rand() {  //僞隨機函數,能讓代碼稍微變快
 2     static int seed=703;
 3     return seed=int(seed*48271LL%(~0u>>1));
 4 }
 5 struct Node {
 6     int val,key,siz,ch[2];
 7 // val 真實權值,key 隨機權值,siz 子樹大小 , ch 左右子節點
 8     void clear() {  //清空操做
 9         ch[0]=ch[1]=siz=val=key=0;
10     }
11 } t[M];
12 int update(int now){ //更新操做
13     t[now].siz=t[t[now].ch[0]].siz+t[t[now].ch[1]].siz+1;
14 }
veiw code

 

 

核心操做

merge操做

 

模型實現

 

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

 

此時要合併 x , y 。咱們先比較它們的根的隨機權值,發現1<3,由於要知足小根堆性質,因而 x 的左子樹所有不變,讓它的右子樹繼續和 y 合併。code

這時咱們發現,隨機權值 key 5>3,因此 y 接到 rot 的下方,成爲 rot 的右兒子,y的右子樹所有不變,讓y的左子樹繼續和x合併(以知足平衡樹的性質)。htm

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

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

至此,咱們發現 x 爲 0 ,因此直接返回 y ,合併結束。

 

代碼實現

 1 int merge(int u,int v) { // 此時 u 中節點權值均小於 v 中節點權值 
 2     if(!u || !v) return u|v;  //某節點爲空,直接返回另外一節點 
 3     if(t[u].key<t[v].key) { //以此知足 heap 性質 
 4         t[u].ch[1]=merge(t[u].ch[1],v); // u 右子節點與 v 合併, 以知足平衡樹性質 
 5         update(u); return u;
 6     } else {
 7         t[v].ch[0]=merge(u,t[v].ch[0]); // u 與 v 左子節點合併, 以知足平衡樹性質 
 8         update(v); return v;
 9     }
10 }
view code

 

 

split操做

split有兩種拆分方式:

  1. 按權值大小拆分

  2. 按排名大小拆分。

 

模型實現

 

1.按權值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,因此拆分完畢,返回。

2.按排名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.按權值split

1 void split_val(int now,int k,int& x,int& y) {
2     if(!now) return (void)(x=y=0); //節點爲空, return 
3     if(t[now].val<=k) //當前節點和它的左子樹都知足進入左樹的條件 
4         x=now,split_val(t[now].ch[1],k,t[now].ch[1],y);
5     else //當前節點和它的右子樹都知足進入右樹的條件 
6         y=now,split_val(t[now].ch[0],k,x,t[now].ch[0]);
7     update(now);
8 }
view code

 

 

2.按排名split

1 void split_k(int now,int k,int& x,int& y) { //與按權值 split 相似 
2     if(!now) return (void)(x=y=0);
3     update(now);
4     if(t[t[now].ch[0]].siz<k)
5         x=now,split_k(t[now].ch[1],k-t[t[now].ch[0]].siz-1,t[now].ch[1],y);
6     else
7         y=now,split_k(t[now].ch[0],k,x,t[now].ch[0]);
8     update(now);
9 }
view code

 

 

 

 

其餘操做

FHQ treap 的核心操做只有 merge 和 split 兩個,其餘操做都是基於這兩個操做實現的。

插入

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

 

代碼實現

1 void ins(int x) {
2     int u,a,b;
3     t[u=++cnt].key=Rand();
4     t[u].val=x,t[u].siz=1;
5     split_val(root,x,a,b);
6     root=merge(merge(a,u),b);
7 }
view code

 

 

刪除

要刪除x,先將整棵樹以 x-1 爲界按權值split 成a和b,再將 b 以 1 爲界 按排名split 成c和d,則 c 就是要刪除的節點。最後按順序merge a,b,d。

(固然,這是在要刪除節點一定存在的狀況下才能進行的操做,不存在的狀況請自行腦補) 

 

代碼實現

1 void del(int x) {
2     int a,b,c,d;
3     split_val(root,x-1,a,b);
4     split_k(b,1,c,d);
5     t[c].clear(),root=merge(a,d);
6 }
view code

 

 

查詢 x 的排名

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

 

代碼實現

1 int get_rank(int x) {
2     int a,b,c;
3     split_val(root,x-1,a,b);
4     c=t[a].siz+1;
5     root=merge(a,b);
6     return c;
7 }
view code

 

 

查詢排名爲 k 的值

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

 

代碼實現

1 int get_val(int& now,int x) {
2     int a,b,c,d,e;
3     split_k(now,x-1,a,b);
4     split_k(b,1,c,d);
5     e=t[c].val;
6     now=merge(a,merge(c,d));
7     return e;
8 }
view code

 

 

查x前驅

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

 

代碼實現

1 int pre(int x) {
2     int a,b,c;
3     split_val(root,x-1,a,b);
4     c=get_val(a,t[a].siz);
5     root=merge(a,b);
6     return c;
7 }
view code

 

 

查x後繼

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

 

代碼實現

1 int sub(int x) {
2     int a,b,c;
3     split_val(root,x,a,b);
4     c=get_val(b,1);
5     root=merge(a,b);
6     return c;
7 }
view code

 

 

非旋 Treap的其餘做用

非旋 trap 是支持區間操做的,具體其實就是你把原來的一棵樹 split 成 3 棵樹($1~l-1,l~r,r+1~n$),而後 咱們對中間那棵樹進行操做便可,具體代碼不附上了

 

例題

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

 

代碼

  1 //by Judge
  2 #include<iostream>
  3 #include<cstdio>
  4 using namespace std;
  5 const int M=1e5+111;
  6 #define getc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
  7 char buf[1<<21],*p1=buf,*p2=buf;
  8 inline int read(){
  9     #define num ch-'0'
 10     char ch;bool flag=0;int res;
 11     while(!isdigit(ch=getc()))
 12     (ch=='-')&&(flag=true);
 13     for(res=num;isdigit(ch=getc());res=res*10+num);
 14     (flag)&&(res=-res);
 15     #undef num
 16     return res;
 17 }
 18 char sr[1<<21],z[20];int C=-1,Z;
 19 inline void Ot(){fwrite(sr,1,C+1,stdout),C=-1;}
 20 inline void print(int x){
 21     if(C>1<<20)Ot();if(x<0)sr[++C]=45,x=-x;
 22     while(z[++Z]=x%10+48,x/=10);
 23     while(sr[++C]=z[Z],--Z);sr[++C]='\n';
 24 }
 25 int n,cnt,root;
 26 int Rand() {
 27     static int seed=703;
 28     return seed=int(seed*48271LL%(~0u>>1));
 29 }
 30 struct Node {
 31     int val,key,siz,ch[2];
 32     void clear() {
 33         ch[0]=ch[1]=siz=val=key=0;
 34     }
 35 } t[M];
 36 int update(int now){
 37     t[now].siz=t[t[now].ch[0]].siz+t[t[now].ch[1]].siz+1;
 38 }
 39 int merge(int u,int v) { 
 40     if(!u || !v) return u|v; 
 41     if(t[u].key<t[v].key) {
 42         t[u].ch[1]=merge(t[u].ch[1],v); 
 43         update(u); return u;
 44     } else {
 45         t[v].ch[0]=merge(u,t[v].ch[0]); 
 46         update(v); return v;
 47     }
 48 }
 49 void split_val(int now,int k,int& x,int& y) {
 50     if(!now) return (void)(x=y=0); 
 51     if(t[now].val<=k) 
 52         x=now,split_val(t[now].ch[1],k,t[now].ch[1],y);
 53     else 
 54         y=now,split_val(t[now].ch[0],k,x,t[now].ch[0]);
 55     update(now);
 56 }
 57 void split_k(int now,int k,int& x,int& y) {
 58     if(!now) return (void)(x=y=0);
 59     update(now);
 60     if(t[t[now].ch[0]].siz<k)
 61         x=now,split_k(t[now].ch[1],k-t[t[now].ch[0]].siz-1,t[now].ch[1],y);
 62     else
 63         y=now,split_k(t[now].ch[0],k,x,t[now].ch[0]);
 64     update(now);
 65 }
 66 void ins(int x) {
 67     int u,a,b;
 68     t[u=++cnt].key=Rand();
 69     t[u].val=x,t[u].siz=1;
 70     split_val(root,x,a,b);
 71     root=merge(merge(a,u),b);
 72 }
 73 void del(int x) {
 74     int a,b,c,d;
 75     split_val(root,x-1,a,b);
 76     split_k(b,1,c,d);
 77     t[c].clear(),root=merge(a,d);
 78 }
 79 int get_rank(int x) {
 80     int a,b,c;
 81     split_val(root,x-1,a,b);
 82     c=t[a].siz+1;
 83     root=merge(a,b);
 84     return c;
 85 }
 86 int get_val(int& now,int x) {
 87     int a,b,c,d,e;
 88     split_k(now,x-1,a,b);
 89     split_k(b,1,c,d);
 90     e=t[c].val;
 91     now=merge(a,merge(c,d));
 92     return e;
 93 }
 94 int pre(int x) {
 95     int a,b,c;
 96     split_val(root,x-1,a,b);
 97     c=get_val(a,t[a].siz);
 98     root=merge(a,b);
 99     return c;
100 }
101 int sub(int x) {
102     int a,b,c;
103     split_val(root,x,a,b);
104     c=get_val(b,1);
105     root=merge(a,b);
106     return c;
107 }
108 int main() {
109     n=read(); int opt,x;
110     while(n--){
111         opt=read(),x=read();
112         switch(opt){
113             case 1: ins(x); break;
114             case 2: del(x); break;
115             case 3: print(get_rank(x)); break;
116             case 4: print(get_val(root,x)); break;
117             case 5: print(pre(x)); break;
118             case 6: print(sub(x)); break;
119         }
120     } Ot(); return 0;
121 }
view code

 

 

 而後這是壓過行了的:

 1 //by Judge
 2 #include<iostream>
 3 #include<cstdio>
 4 using namespace std;
 5 const int M=1e5+111;
 6 #define getc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
 7 char buf[1<<21],*p1=buf,*p2=buf;
 8 inline int read(){
 9     #define num ch-'0'
10     char ch;bool flag=0;int res;
11     while(!isdigit(ch=getc()))
12     (ch=='-')&&(flag=true);
13     for(res=num;isdigit(ch=getc());res=res*10+num);
14     (flag)&&(res=-res);
15     #undef num
16     return res;
17 }
18 char sr[1<<21],z[20];int C=-1,Z;
19 inline void Ot(){fwrite(sr,1,C+1,stdout),C=-1;}
20 inline void print(int x){
21     if(C>1<<20)Ot();if(x<0)sr[++C]=45,x=-x;
22     while(z[++Z]=x%10+48,x/=10);
23     while(sr[++C]=z[Z],--Z);sr[++C]='\n';
24 }
25 int n,cnt,root;
26 struct Node {
27     int val,key,siz,ch[2];
28     void clear() { ch[0]=ch[1]=siz=val=key=0; }
29 } t[M];
30 int Rand() { static int seed=703; return seed=int(seed*48271LL%(~0u>>1)); }
31 int update(int now){ t[now].siz=t[t[now].ch[0]].siz+t[t[now].ch[1]].siz+1; }
32 int merge(int u,int v) {
33     if(!u || !v) return u|v;
34     if(t[u].key<t[v].key) { t[u].ch[1]=merge(t[u].ch[1],v),update(u); return u; }
35     else { t[v].ch[0]=merge(u,t[v].ch[0]),update(v); return v; }
36 }
37 void split_val(int now,int k,int& x,int& y) {
38     if(!now) return (void)(x=y=0);
39     if(t[now].val<=k) split_val(t[x=now].ch[1],k,t[now].ch[1],y);
40     else split_val(t[y=now].ch[0],k,x,t[now].ch[0]);
41     update(now);
42 }
43 void split_k(int now,int k,int& x,int& y) { 
44     if(!now) return (void)(x=y=0);
45     if(t[t[now].ch[0]].siz>=k) split_k(t[y=now].ch[0],k,x,t[now].ch[0]);
46     else split_k(t[x=now].ch[1],k-t[t[now].ch[0]].siz-1,t[now].ch[1],y);
47     update(now);
48 }
49 void ins(int x) { int u,a,b; t[u=++cnt].key=Rand(),t[u].val=x,t[u].siz=1,split_val(root,x,a,b),root=merge(merge(a,u),b); }
50 void del(int x) { int a,b,c,d; split_val(root,x-1,a,b),split_k(b,1,c,d),t[c].clear(),root=merge(a,d); }
51 int get_rank(int x) { int a,b,c; split_val(root,x-1,a,b),c=t[a].siz+1,root=merge(a,b); return c; }
52 int get_val(int& now,int x) { int a,b,c,d,e; split_k(now,x-1,a,b),split_k(b,1,c,d),e=t[c].val,now=merge(a,merge(c,d)); return e; }
53 int pre(int x) { int a,b,c; split_val(root,x-1,a,b),c=get_val(a,t[a].siz),root=merge(a,b); return c; }
54 int sub(int x) { int a,b,c; split_val(root,x,a,b),c=get_val(b,1),root=merge(a,b); return c; }
55 int main() {
56     n=read(); int opt,x;
57     while(n--){
58         opt=read(),x=read();
59         switch(opt){
60             case 1: ins(x); break;
61             case 2: del(x); break;
62             case 3: print(get_rank(x)); break;
63             case 4: print(get_val(root,x)); break;
64             case 5: print(pre(x)); break;
65             case 6: print(sub(x)); break;
66         }
67     } Ot(); return 0;
68 }
view code

 

 

推薦題目

題目

洛谷 ——  列隊

 

分析

首先咱們分析一下這個列隊的性質;

首先每次操做時,先讓一我的 (a,b) 出隊  。

而後右邊的人跟上來,空缺的位置變成了 (a,m)   (也就是在一棵平衡樹中刪除了一個節點)

 

 

 

最後其實也就是最後一列的跟到上面,空缺的位置變成 (n,m) ,而後出隊的人到這個位置上

 

 

 

因而很是顯然的,題目就是要你維護 n 棵平衡樹,

可是看看數據範圍:3e5  ...  因而發現這題不可作

那麼我萌就要用到一個巧妙的思路—— 縮點與拆點  了。

什麼意思? 你再看眼數據範圍:3e5  ...  沒毛病?

對啊,詢問也是  3e5  啊...  因此說咱們是否是維護了許多用都用不到的點呢?

因而咱們把一大段區間的沒用的點縮成一個點,讓這個點記錄區間左右信息就行了咯。

emmm...你說的沒錯,咱們怎麼知道哪些點該縮哪些點不應縮呢?

簡單,咱們點全都縮起來,要用的時候再拆出來不就行了?

而後還要注意的就是最後一列的特殊性,要單獨維護(也就 3e5 個點嘛,開得下),

這點從圖中應該也看得出來(況且這些點並不屬於一個區間!)

因而愉快地上代碼...

 

 

 

代碼

 1 //by Judge
 2 #include<iostream>
 3 #include<cstdio>
 4 #define ll long long
 5 #define int long long
 6 using namespace std;
 7 const int M=3e5+111;
 8 #define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)    // 手動輸入須要去掉這句話 
 9 char buf[1<<21],*p1=buf,*p2=buf;
10 inline int read(){ 
11     int x=0,f=1; char c=getchar();
12     for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
13     for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f;
14 }
15 char sr[1<<21],z[20];int C=-1,Z;
16 inline void Ot(){fwrite(sr,1,C+1,stdout),C=-1;}
17 inline void print(int x){
18     if(C>1<<20)Ot();if(x<0)sr[++C]=45,x=-x;
19     while(z[++Z]=x%10+48,x/=10);
20     while(sr[++C]=z[Z],--Z);sr[++C]='\n';
21 }
22 ll n,m,q,cnt,root,rt[M];
23 struct Node { int key,ch[2]; ll l,r,siz; } t[M<<4];
24 inline int Rand() { static int seed=703; return seed=(ll)(seed*48271LL%(~0u>>1)); }
25 inline int update(int now){ t[now].siz=t[t[now].ch[0]].siz+t[t[now].ch[1]].siz+t[now].r-t[now].l+1; }
26 inline int newnode(ll x,ll y) { int u=++cnt; t[u].l=x,t[u].r=y,t[u].siz=y-x+1,t[u].key=Rand(); return u; }
27 int merge(int u,int v) {
28     if(!u || !v) return u|v;
29     if(t[u].key<t[v].key) { t[u].ch[1]=merge(t[u].ch[1],v),update(u); return u; }
30     else { t[v].ch[0]=merge(u,t[v].ch[0]),update(v); return v; }
31 }
32 void split_new(ll now,ll k){  //把一個節點拆成兩個節點 
33     if(k>=t[now].r-t[now].l+1) return ; int nw=newnode(t[now].l+k,t[now].r);
34     t[now].r=t[now].l+k-1,t[now].ch[1]=merge(nw,t[now].ch[1]),update(now);
35 }
36 void split(int now,int k,int& x,int& y) {  //常規操做 
37     if(!now) return (void)(x=y=0);
38     if(t[t[now].ch[0]].siz>=k) split(t[y=now].ch[0],k,x,t[now].ch[0]);
39     else{
40         split_new(now,k-t[t[now].ch[0]].siz),k-=t[t[now].ch[0]].siz,
41         split(t[x=now].ch[1],k-(t[now].r-t[now].l+1),t[now].ch[1],y);
42     } update(now);
43 }
44 signed main(){
45     n=read(),m=read(),q=read();
46     for(ll i=1;i<=n;++i)
47         rt[i]=newnode((i-1)*m+1,i*m-1);
48     for(ll i=1;i<=n;++i)
49         rt[n+1]=merge(rt[n+1],newnode(i*m,i*m));
50     while(q--){
51         ll a=read(),b=read(),x,y,z;
52         if(b^m){
53             ll xx,yy,zz; split(rt[a],b,x,y),
54             split(x,b-1,x,z),print(t[z].l),
55             split(rt[n+1],a,xx,yy),split(xx,a-1,xx,zz);
56             rt[a]=merge(merge(x,y),zz),rt[n+1]=merge(merge(xx,yy),z);  //拆完合併 
57         }
58         else{
59             split(rt[n+1],a,x,y),split(x,a-1,x,z);
60             print(t[z].l),rt[n+1]=merge(merge(x,y),z);
61         }
62     } Ot(); return 0;
63 }
View Code

 

 

 

 

 

 

 

 

 

最後感謝 axjcy 大佬的 blog 

相關文章
相關標籤/搜索