替罪羊樹不依靠旋轉,而是依靠重構不平衡的子樹使整棵樹達到平衡狀態。php
1 int n; 2 struct Node { 3 int val,num; //節點的值,數量 4 int siz,hid; //以該節點爲根的子樹中未被刪的節點數和已被刪的節點數 5 int son[2],fa; //左(0)右(1)兒子,爸爸 6 char id; //是否被刪(0否1是) 7 void res() { //清空,用於重構 8 hid=fa=val=num=siz=son[0]=son[1]=id=0; 9 } 10 } tree[maxn]; 11 int ins,mem[maxn],inm,root; //新節點內存回收池 12 int reb[maxn],ren[maxn],inr; //臨時數組,重構用
當一顆子樹不平衡時,便將其重構,這是替罪羊樹的核心思想。判斷一顆子樹是否平衡的方法有不少,我採用的方法是,若一顆子樹的某個兒子子樹的大小大於這顆子樹的大小的alpha倍,或這顆子樹被刪除的節點數超過這顆子樹總結點數的alpha倍,則認爲這顆子樹是不平衡的,須要重構。咱們也能夠知道,當alpha>=1或alpha<=0.5的時候無心義。alpha越大,重構次數越少,但這顆樹越不平衡;alpha越小,樹越平衡,但重構次數越多。所以,咱們折中取alpha=0.75。(因爲不想用浮點數,這裏改爲了整數乘法運算)html
1 char need_to_reset(int now) { //以當前節點爲根的子樹是否須要重構 2 return tree[now].siz*3<tree[tree[now].son[0]].siz*4 3 ||tree[now].siz*3<tree[tree[now].son[1]].siz*4||tree[now].siz<tree[now].hid*3; //若某顆子樹大小超過整棵子樹的3/4或 4 //被刪除的節點超過總結點數的3/4則需重構 5 }
重構有兩種方法,我採用的是時空複雜度O(n)的中序遍歷法(ps:所謂拍扁重構法是用鏈表的時間O(n)空間O(logn)的方法,本蒟蒻不會)。將須要的子樹中序遍歷,獲得一個序列,再每次二分,取最中間的那個數建根,左右遞歸建樹。c++
1 void redfs(int now) { 2 if(tree[now].son[0]) redfs(tree[now].son[0]); 3 if(!tree[now].id)reb[++inr]=tree[now].val,ren[inr]=tree[now].num; //若沒被刪除則加入臨時數組,不然不加入 4 if(tree[now].son[1]) redfs(tree[now].son[1]); 5 mem[++inm]=now; 6 tree[now].res(); //清空 7 } 8 void rebuild(int l,int r,int pla,int f) { 9 if(l>r)return; 10 if(l==r) { 11 tree[pla].val=reb[l]; 12 tree[pla].siz=tree[pla].num=ren[l]; 13 tree[pla].fa=f; 14 return; 15 } 16 int mid=l+r>>1; 17 tree[pla].val=reb[mid]; 18 int s1,s2; 19 tree[pla].siz=tree[pla].num=ren[mid]; 20 tree[pla].fa=f; 21 if(l<mid) { //建左子樹 22 s1=tree[pla].son[0]=mem[inm--]; 23 rebuild(l,mid-1,s1,pla); 24 } 25 if(mid<r) { //建右子樹 26 s2=tree[pla].son[1]=mem[inm--]; 27 rebuild(mid+1,r,s2,pla); 28 } 29 update(pla); 30 } 31 void reset(int rot) { 32 int f=tree[rot].fa; 33 inr=0; //這個清零應該不會忘 34 redfs(rot); 35 rebuild(1,inr,((root==rot)?root=mem[inm--]:mem[inm--]),f); //注意,若需重構整棵樹則要更新root 36 for(;tree[rot].fa;rot=tree[rot].fa)update(tree[rot].fa); 37 }
插入git
和普通的平衡樹同樣插入,注意要用遞歸,而且傳參的時候傳一個引用,用來存回溯時最淺的須要被重構的子樹根編號。數組
1 void insert(int now,int x,int &ntr) { //ntr爲引用,由於須要在回溯時找到最淺的替罪羊節點 2 //注意必定要用遞歸式的,用循環式的就會像我同樣WA上天 3 //刪除也是同樣 4 if(!now) { //走到這個if裏面說明整棵樹爲空 5 int u=(inm?mem[inm--]:++ins); 6 tree[u].siz=tree[u].num=1; 7 tree[u].val=x; 8 root=u; 9 return; 10 } 11 if(tree[now].val==x) { //當前節點的值等於需插入的值 12 if(tree[now].id) tree[now].id=0,tree[now].hid--; //若是被刪除則恢復 13 tree[now].siz++,tree[now].num++; 14 if(need_to_reset(now)) ntr=now; //隨手一判 15 return; 16 } 17 if(tree[now].son[tree[now].val<x]) insert(tree[now].son[tree[now].val<x],x,ntr);//往正確的兒子走 18 else { //走到底了 19 int u=(inm?mem[inm--]:++ins); 20 tree[u].siz=tree[u].num=1; 21 tree[u].fa=now; 22 tree[u].val=x; 23 tree[now].son[tree[now].val<x]=u; 24 } 25 update(now); 26 if(need_to_reset(now)) ntr=now; 27 }
替罪羊樹的刪除並非真正意義上的刪除,而是在這個數被刪除消失時打上一個刪除標記,當重構時再真正刪除,或再次添加這個數來取消這個標記。(ps:這個思想在許多數據結構中都很是有用)數據結構
1 void delet(int now,int x,int &ntr) { 2 if(tree[now].val==x) { 3 if(tree[now].num) tree[now].num--,tree[now].siz--,tree[now].hid++; 4 if(!tree[now].num) tree[now].id=1; 5 if(need_to_reset(now)&&(tree[now].son[0]||tree[now].son[1])) ntr=now; //若是這個點是葉子節點就不判,不要問我爲何 6 return; 7 } 8 if(tree[now].son[tree[now].val<x]) delet(tree[now].son[tree[now].val<x],x,ntr); 9 update(now); 10 if(need_to_reset(now)) ntr=now; 11 }
和其餘平衡樹同樣查找就好了,無所謂遞歸或循環。ide
1 int findran(int x) { 2 int u=root,ans=1; 3 for(; u&&tree[u].val!=x; u=tree[u].son[x>tree[u].val]) //不須要判重構,因此能夠用循環 4 ans+=(x>tree[u].val)*(tree[tree[u].son[x<tree[u].val]].siz+tree[u].num); 5 if(u) ans+=tree[tree[u].son[0]].siz; 6 return ans; 7 }
和其餘平衡樹同樣查找就好了。函數
1 int findnum(int x) { 2 int u=root; 3 for(char t; tree[u].son[tree[tree[u].son[0]].siz+tree[u].num<x?1:0]&& 4 (tree[tree[u].son[0]].siz>=x||tree[tree[u].son[0]].siz+tree[u].num<x); 5 t=tree[tree[u].son[0]].siz+tree[u].num<x, 6 x-=t*(tree[tree[u].son[0]].siz+tree[u].num),u=tree[u].son[t]); 7 return tree[u].val; 8 }
先找到這個數的排名y,再找到排名爲y-1的數就是x的前驅。ui
1 int las(int x) { 2 int rnk=findran(x); //先找到這個點的排名 3 return findnum(rnk-1); //再找到比它小的最大的數 4 }
先找到這個數的最大排名(即這個數的排名+這個數的個數-1)y,再找到排名爲y+1的數就是x的後繼。spa
1 int nex(int x) { 2 int u=root,ans=0; //不要問我爲何這個寫得這麼醜 3 for(; u&&tree[u].val!=x; u=tree[u].son[x>tree[u].val]) //找到這個數的最大排名 4 ans+=(x>tree[u].val)*(tree[tree[u].son[x<tree[u].val]].siz+tree[u].num); 5 if(u) ans+=tree[tree[u].son[0]].siz+tree[u].num; 6 return findnum(ans+1); //查詢比它大的最小的數 7 }
重構:單次重構的複雜度爲O(n),但經過勢能分析能夠得出重構的均攤複雜度爲O(logn)。推導過程來自VFleaking大佬的論文(Orzzzzzz)。
插入、刪除、查詢:因爲保證樹高均攤logn,所以複雜度爲均攤O(logn)
常數還能夠,彷佛僅比紅黑樹慢,但單一的alpha容易被卡。所以能夠開始就隨機若干個alpha,取出合格的取平均值
O(n)
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,num;int siz,hid;int son[2],fa;char id;void res(){hid=fa=val=num=siz=son[0]=son[1]=id=0;}}tree[maxn]; 14 int ins,mem[maxn],inm,root; 15 int reb[maxn],ren[maxn],inr; 16 template<class T> 17 inline T read(T &n){ 18 n=0;int t=1;double x=10;char ch; 19 for(ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());(ch=='-')?t=-1:n=ch-'0'; 20 for(ch=getchar();isdigit(ch);ch=getchar()) n=n*10+ch-'0'; 21 if(ch=='.') for(ch=getchar();isdigit(ch);ch=getchar()) n+=(ch-'0')/x,x*=10; 22 return (n*=t); 23 }void update(int x){ 24 tree[x].siz=tree[tree[x].son[0]].siz+tree[tree[x].son[1]].siz+tree[x].num; 25 tree[x].hid=tree[tree[x].son[0]].hid+tree[tree[x].son[1]].hid+tree[x].id; 26 }void redfs(int now){if(tree[now].son[0]) redfs(tree[now].son[0]); 27 if(!tree[now].id)reb[++inr]=tree[now].val,ren[inr]=tree[now].num; 28 if(tree[now].son[1]) redfs(tree[now].son[1]);mem[++inm]=now;tree[now].res(); 29 }void rebuild(int l,int r,int pla,int f){if(l>r)return; 30 if(l==r){tree[pla].val=reb[l];tree[pla].siz=tree[pla].num=ren[l];tree[pla].fa=f;return;} 31 int mid=l+r>>1;tree[pla].val=reb[mid];int s1,s2;tree[pla].siz=tree[pla].num=ren[mid];tree[pla].fa=f; 32 if(l<mid){s1=tree[pla].son[0]=mem[inm--];rebuild(l,mid-1,s1,pla);} 33 if(mid<r){s2=tree[pla].son[1]=mem[inm--];rebuild(mid+1,r,s2,pla);}update(pla); 34 }void reset(int rot){ 35 int f=tree[rot].fa;inr=0;redfs(rot);rebuild(1,inr,((root==rot)?root=mem[inm--]:mem[inm--]),f); 36 for(;tree[rot].fa;rot=tree[rot].fa)update(tree[rot].fa); 37 }char need_to_reset(int now){return tree[now].siz*3<tree[tree[now].son[0]].siz*4 38 ||tree[now].siz*3<tree[tree[now].son[1]].siz*4||tree[now].siz<tree[now].hid*3; 39 }void insert(int now,int x,int &ntr){if(!now){int u=(inm?mem[inm--]:++ins); 40 tree[u].siz=tree[u].num=1;tree[u].val=x;root=u;return;} 41 if(tree[now].val==x){if(tree[now].id) tree[now].id=0,tree[now].hid--; 42 tree[now].siz++,tree[now].num++;if(need_to_reset(now)) ntr=now;return; 43 }if(tree[now].son[tree[now].val<x]) insert(tree[now].son[tree[now].val<x],x,ntr); 44 else{int u=(inm?mem[inm--]:++ins);tree[u].siz=tree[u].num=1;tree[u].fa=now; 45 tree[u].val=x;tree[now].son[tree[now].val<x]=u; 46 }update(now);if(need_to_reset(now)) ntr=now; 47 }void delet(int now,int x,int &ntr){ 48 if(tree[now].val==x){if(tree[now].num) tree[now].num--,tree[now].siz--,tree[now].hid++; 49 if(!tree[now].num) tree[now].id=1;if(need_to_reset(now)&&(tree[now].son[0]||tree[now].son[1])) ntr=now;return; 50 }if(tree[now].son[tree[now].val<x]) delet(tree[now].son[tree[now].val<x],x,ntr); 51 update(now);if(need_to_reset(now)) ntr=now; 52 }int findran(int x){int u=root,ans=1; 53 for(;u&&tree[u].val!=x;u=tree[u].son[x>tree[u].val]) ans+=(x>tree[u].val)*(tree[tree[u].son[x<tree[u].val]].siz+tree[u].num); 54 if(u) ans+=tree[tree[u].son[0]].siz;return ans; 55 }int findnum(int x){int u=root; 56 for(char t;tree[u].son[tree[tree[u].son[0]].siz+tree[u].num<x?1:0]&&(tree[tree[u].son[0]].siz>=x||tree[tree[u].son[0]].siz+tree[u].num<x); 57 t=tree[tree[u].son[0]].siz+tree[u].num<x,x-=t*(tree[tree[u].son[0]].siz+tree[u].num),u=tree[u].son[t]);return tree[u].val; 58 }int las(int x){int rnk=findran(x);return findnum(rnk-1);} 59 int nex(int x){int u=root,ans=0; 60 for(;u&&tree[u].val!=x;u=tree[u].son[x>tree[u].val]) ans+=(x>tree[u].val)*(tree[tree[u].son[x<tree[u].val]].siz+tree[u].num); 61 if(u) ans+=tree[tree[u].son[0]].siz+tree[u].num;return findnum(ans+1); 62 }int main(){ 63 read(n); 64 fui(i,1,n,1){ 65 int opt,x,y=0;read(opt);read(x); 66 switch(opt){ 67 case 1:insert(root,x,y);if(y) reset(y);break; 68 case 2:delet(root,x,y);if(y) reset(y);break; 69 case 3:cout<<findran(x)<<endl;break; 70 case 4:cout<<findnum(x)<<endl;break; 71 case 5:cout<<las(x)<<endl;break; 72 case 6:cout<<nex(x)<<endl; 73 } 74 } 75 return 0; 76 }
注意以上兩份代碼只有insert函數和delet函數不一樣。AC代碼爲遞歸式的,60分代碼爲循環式的。我估計是循環的時候沒法維護每一個節點的hid值(至於你說找到這個點後再循環回根update,本蒟蒻也打過,而且TLE,仍是60分。。。)
直接上題吧
洛谷P4278 帶插入區間K小值/BZOJ3065 帶插入區間K小值
解法之一:平衡樹套線段樹(替罪羊樹套主席樹)(然而本蒟蒻不會。。。)
(ps:這題基本別想在洛谷A掉。此題堪稱洛谷最難題。在BZOJ上4個點共60s,洛谷上5個點各1s,目前沒有一我的A掉)
又是引進VFleaking大佬的(Orzzzzzz)