[學習筆記]FHQ-Treap及其可持久化
感覺範浩強真的巨
博主只刷了模板所以就講基礎
fhq-treap
又形象的稱為非旋轉treap
顧名思義
保留了treap的隨機數堆的特點,並以此作為複雜度正確的條件
並且所有的實現不用旋轉!
思路自然,結構直觀,程式碼簡潔,理解輕鬆。
雖然不能支援LCT(起碼我不會)
但是相較於splay可以可持久化(splay理論上旋轉會造成空間爆炸)
基本和splay平分秋色,甚至更勝一籌
核心操作只有兩個:
merge:把兩個樹合成一個樹
int merge(int x,int y){ if(!x||!y) return x+y; if(t[x].pri<t[y].pri){ t[x].ch[1]=merge(t[x].ch[1],y); pushup(x); return x; }else{ t[y].ch[0]=merge(x,t[y].ch[0]); pushup(y); return y; } }
(merge最後只有一棵樹,所以可以帶返回值)
需要注意的是,merge的兩棵樹,預設x的所有權值都小於y!
split:把一個樹分裂成兩個樹
按照權值k分裂
void split(int now,int k,int &x,int &y){ if(!now){ x=0;y=0;return; } if(t[now].val<=k){ x=now; split(t[now].ch[1],k,t[now].ch[1],y); }else{ y=now; split(t[now].ch[0],k,x,t[now].ch[0]); } pushup(now); }
按照sz分裂:(維護序列時候)
void split(int now,int k,int &x,int &y){ if(!now) { x=0;y=0;return; } pushdown(now); if(t[t[now].ch[0]].sz+1<=k){ k-=t[t[now].ch[0]].sz+1; x=now; split(t[now].ch[1],k,t[now].ch[1],y); }else{ y=now; split(t[now].ch[0],k,x,t[now].ch[0]); } pushup(now); }
(由於split最後得到兩棵樹,所以只能用&)
模擬一下很直觀的。
其他的操作直接看程式碼就可以理解:
fhq不用記錄father的
#include<bits/stdc++.h> #define reg register int #define il inline #define numb (ch^'0') using namespace std; typedef long long ll; il void rd(int &x){ char ch;x=0;bool fl=false; while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true); for(x=numb;isdigit(ch=getchar());x=x*10+numb); (fl==true)&&(x=-x); } namespace Miracle{ const int N=100000+5; const int inf=0x3f3f3f3f; struct node{ int pri; int ch[2]; int val; int sz; }t[N]; int tot; int rt; int n; int nc(int v){ t[++tot].val=v;t[tot].sz=1; t[tot].pri=rand(); return tot; } void pushup(int x){ t[x].sz=t[t[x].ch[1]].sz+t[t[x].ch[0]].sz+1; } int merge(int x,int y){ if(!x||!y) return x+y; if(t[x].pri<t[y].pri){ t[x].ch[1]=merge(t[x].ch[1],y); pushup(x); return x; }else{ t[y].ch[0]=merge(x,t[y].ch[0]); pushup(y); return y; } } void split(int now,int k,int &x,int &y){ if(!now){ x=0;y=0;return; } if(t[now].val<=k){ x=now; split(t[now].ch[1],k,t[now].ch[1],y); }else{ y=now; split(t[now].ch[0],k,x,t[now].ch[0]); } pushup(now); } int kth(int now,int k){ int x=now; while(1){ if(t[t[x].ch[0]].sz+1==k) return x; int tmp=t[t[x].ch[0]].sz+1; if(tmp<k){ k-=tmp;x=t[x].ch[1]; }else{ x=t[x].ch[0]; } } } int main(){ srand(19260817); rd(n); int op,x; int le=0,ri=0; while(n--){ rd(op);rd(x); switch (op){ case 1:{ split(rt,x,le,ri); rt=merge(merge(le,nc(x)),ri); break; } case 2:{ int zz; split(rt,x,le,zz); split(le,x-1,le,ri); //le=t[le].ch[0];//warning!!! maybe wrong ri=merge(t[ri].ch[0],t[ri].ch[1]); rt=merge(merge(le,ri),zz); break; } case 3:{ split(rt,x-1,le,ri); printf("%d\n",t[le].sz+1); rt=merge(le,ri); break; } case 4:{ int tmp=kth(rt,x); printf("%d\n",t[tmp].val); break; } case 5:{ split(rt,x-1,le,ri); int tmp=kth(le,t[le].sz); printf("%d\n",t[tmp].val); rt=merge(le,ri); break; } case 6:{ split(rt,x,le,ri); int tmp=kth(ri,1); printf("%d\n",t[tmp].val); rt=merge(le,ri); break; } } } return 0; } } signed main(){ Miracle::main(); return 0; } /* Author: *Miracle* Date: 2019/1/5 21:36:18 */View Code
值得一提的是
fhq可以有重複的點
在求第k大的時候直接二分不會出問題
其他時候可能會有問題。。
所以其他時候都要split和merge
左子樹,右子樹的值可能存在和自己相等的情況
但是由於merge的大小一定是x小於y,所以不會出現右子樹有比自己小的,左子樹有比自己大的。
所以還是可以直接找第k大的。(當然按照size分裂也可以)
大概是這樣吧
區間操作?
分成三棵樹[1,l-1],[l,r],[r+1,n]
對於中間的樹打上標記或者查詢詢問
然後依次merge起來
值得一提的是,可以不用加入什麼0,n+1兩個節點,空樹在fhq-treap中不會有任何影響
(splay就有點難受了,把0splay到根會出各種各樣的問題。因為0作為哨兵,father,ch都是假的)
然後就可以做文藝平衡樹了:
#include<bits/stdc++.h> #define il inline #define reg register int #define numb (ch^'0') using namespace std; typedef long long ll; il void rd(int &x){ char ch;bool fl=false; while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true); for(x=numb;isdigit(ch=getchar());x=x*10+numb); (fl==true)&&(x=-x); } namespace Miracle{ const int N=100000+5; int n; struct node{ int rev,ch[2]; int sz; int val; int pri; }t[N]; int tot; int rt; int nc(int c){ ++tot;t[tot].val=c;t[tot].pri=rand();t[tot].sz=1; return tot; } void pushup(int x){ t[x].sz=t[t[x].ch[1]].sz+t[t[x].ch[0]].sz+1; } void rev(int x){ swap(t[x].ch[0],t[x].ch[1]); t[x].rev^=1; } void pushdown(int x){ if(t[x].rev){ rev(t[x].ch[0]);rev(t[x].ch[1]); t[x].rev=0; } } int merge(int x,int y){ if(!x||!y) return x+y; if(t[x].pri<t[y].pri){ pushdown(x); t[x].ch[1]=merge(t[x].ch[1],y); pushup(x); return x; } else{ pushdown(y); t[y].ch[0]=merge(x,t[y].ch[0]); pushup(y); return y; } } void split(int now,int k,int &x,int &y){ if(!now) { x=0;y=0;return; } pushdown(now); if(t[t[now].ch[0]].sz+1<=k){ k-=t[t[now].ch[0]].sz+1; x=now; split(t[now].ch[1],k,t[now].ch[1],y); }else{ y=now; split(t[now].ch[0],k,x,t[now].ch[0]); } pushup(now); } void op(int x){ pushdown(x); if(t[x].ch[0]) op(t[x].ch[0]); if(t[x].val>0) printf("%d ",t[x].val); if(t[x].ch[1]) op(t[x].ch[1]); } int main(){ srand(19260817); rd(n); rt=nc(-23333); for(reg i=1;i<=n;++i) rt=merge(rt,nc(i)); rt=merge(rt,nc(-66666)); int m; rd(m); int l,r; int x,y,z; while(m--){ rd(l);rd(r); split(rt,l,x,z); split(z,r-l+1,z,y); rev(z); rt=merge(merge(x,z),y); } op(rt); return 0; } } signed main(){ Miracle::main(); return 0; } /* Author: *Miracle* Date: 2019/1/6 8:41:45 */View Code
最厲害的是,fhq可以可持久化!
由於出色的簡單操作和結構的相對穩定性,使得一次操作不會產生太多的點
(其實splay均攤操作logn,旋轉暴力建節點也是可以的吧,,但是常數和空間都大到飛起~)
總之完爆splay幾條街
具體只用多幾行:
split的時候,對於劈出來的這條鏈上每個點都建立一個新節點
merge的時候,合併出來的點也是新的節點
正確性的話,只要不會影響之前版本的查詢,顯然就沒有問題
形成了二子多父的實際局面(和主席樹也是一樣的)
然後每個版本的根記錄好即可。
垃圾回收還是有必要的
而且發現,merge總在split之後,split已經把新節點建好了。所以merge可以不建節點
注意第k大,保證有sz才去找,否則就RE辣。
程式碼:
#include<bits/stdc++.h> #define il inline #define reg register int #define numb (ch^'0') using namespace std; typedef long long ll; il void rd(int &x){ char ch;bool fl=false; while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true); for(x=numb;isdigit(ch=getchar());x=x*10+numb); (fl==true)&&(x=-x); } namespace Miracle{ const int N=5e5+5; const int inf=2147483647; struct node{ int val,sz; int ch[2],pri; }t[N*60]; int tot; int rt[N]; int m; int dp[N],top; int nc(int v){ int r=top?dp[top--]:++tot; t[r].val=v;t[r].sz=1; t[r].pri=rand();t[r].ch[0]=t[r].ch[1]=0; return r; } int copy(int x){ int r=top?dp[top--]:++tot; t[r]=t[x];return r; } void pushup(int x){ t[x].sz=t[t[x].ch[0]].sz+t[t[x].ch[1]].sz+1; } int merge(int x,int y){ if(!x||!y) return x+y; if(t[x].pri<t[y].pri){ //int now=copy(x); t[x].ch[1]=merge(t[x].ch[1],y); pushup(x); return x; } else{ // int now=copy(y); t[y].ch[0]=merge(x,t[y].ch[0]); pushup(y); return y; } } void split(int now,int k,int &x,int &y){ if(!now){ x=0;y=0;return; } if(t[now].val<=k){ x=copy(now); split(t[now].ch[1],k,t[x].ch[1],y); pushup(x); } else{ y=copy(now); split(t[now].ch[0],k,x,t[y].ch[0]); pushup(y); } } int kth(int now,int k){ int x=now; // cout<<" now "<<now<<" "<<t[now].sz<<" and "<<k<<endl; if(t[now].sz==0) { // cout<<" ??? "<<endl; return inf; } while(1){ // cout<<x<<endl; if(t[t[x].ch[0]].sz+1==k) return x; int tmp=t[t[x].ch[0]].sz+1; if(tmp<k) k-=tmp,x=t[x].ch[1]; else x=t[x].ch[0]; } return 23333; } int main(){ srand(19260817); int n; rd(n); int st,op,a; int x,y,z; for(reg i=1;i<=n;++i){ rd(st);rd(op);rd(a); if(op==1){ split(rt[st],a,x,y); //cout<<" after split "<<x<<" "<<y<<endl; rt[i]=merge(merge(x,nc(a)),y); //cout<<" rt "<<rt[i]<<" "<<t[rt[i]].sz<<" "<<t[rt[i]].val<<" :: "<<t[rt[i]].ch[0]<<" "<<t[rt[i]].ch[1]<<endl; }else if(op==2){ split(rt[st],a,x,y); // cout<<" after split "<<x<<" "<<y<<endl; if(t[x].sz==0||t[kth(x,t[x].sz)].val!=a) {//no exist // cout<<" no exist "<<endl; rt[i]=merge(x,y); continue; } // cout<<" after check "<<endl; split(x,a-1,x,z); //cout<<" after split2 "<<endl; z=merge(t[z].ch[0],t[z].ch[1]); //cout<<" after dele "<<endl; rt[i]=merge(merge(x,z),y); //cout<<" after merge=end"<<endl; }else if(op==3){ split(rt[st],a-1,x,y); printf("%d\n",t[x].sz+1); rt[i]=merge(x,y); }else if(op==4){ rt[i]=rt[st]; printf("%d\n",t[kth(rt[i],a)].val); }else if(op==5){ split(rt[st],a-1,x,y); if(t[x].sz==0){ printf("%d\n",-inf); }else{ printf("%d\n",t[kth(x,t[x].sz)].val); } rt[i]=merge(x,y); }else{ split(rt[st],a,x,y); if(t[y].sz==0){ printf("%d\n",inf); }else{ printf("%d\n",t[kth(y,1)].val); } rt[i]=merge(x,y); } } return 0; } } signed main(){ Miracle::main(); return 0; } /* Author: *Miracle* Date: 2019/1/6 11:11:11 */View Code
這個模板太樸素
我第一次用主席樹水過去了,還有人用可持久化0/1trie
嘗試真正平衡樹才能做的:
區間翻轉!序列插入一個數!序列刪除一個數!
然後有了此題:
區間翻轉的標記下放的時候
如果有兒子,就新建一個。沒有這個兒子就不用建了。空節點還是不能隨便建的。否則TLE爆炸
用上垃圾回收和merge不用建立節點節省空間
就可以過啦。
#include<bits/stdc++.h> #define il inline #define reg register int #define numb (ch^'0') using namespace std; typedef long long ll; il void rd(int &x){ char ch;bool fl=false; while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true); for(x=numb;isdigit(ch=getchar());x=x*10+numb); (fl==true)&&(x=-x); } namespace Miracle{ const int N=2e5+5; int n; int rt[N]; struct node{ int pri,sz; int ch[2],rev; ll sum,val; }t[N*60]; int tot; int dp[N],top; int nc(int v){ int r=top?dp[top--]:++tot; t[r].val=v;t[r].sum=v;t[r].sz=1; t[r].pri=rand();t[r].ch[0]=t[r].ch[1]=0; t[r].rev=0; return r; } int cpy(int x){ int r=top?dp[top--]:++tot; t[r]=t[x];return r; } void pushup(int x){ if(!x) return; t[x].sz=t[t[x].ch[0]].sz+t[t[x].ch[1]].sz+1; t[x].sum=t[t[x].ch[0]].sum+t[t[x].ch[1]].sum+t[x].val; } void pushdown(int x){ if(!x) return; if(t[x].rev){ if(t[x].ch[1]&&t[x].ch[0]){ int ls=cpy(t[x].ch[1]),rs=cpy(t[x].ch[0]); t[x].ch[0]=ls; t[x].ch[1]=rs; t[ls].rev^=1;t[rs].rev^=1; }else if(t[x].ch[1]){ int ls=cpy(t[x].ch[1]); t[x].ch[0]=ls; t[x].ch[1]=0;t[ls].rev^=1; }else if(t[x].ch[0]){ int rs=cpy(t[x].ch[0]); t[x].ch[1]=rs; t[x].ch[0]=0;t[rs].rev^=1; } t[x].rev=0; pushup(x); } } int merge(int x,int y){ if(!x||!y) return x+y; if(t[x].pri<t[y].pri){ pushdown(x); //int now=cpy(x); t[x].ch[1]=merge(t[x].ch[1],y); pushup(x); return x; }else{ pushdown(y); //int now=cpy(y); t[y].ch[0]=merge(x,t[y].ch[0]); pushup(y); return y; } } void split(int now,int k,int &x,int &y){ //cout<<" spliting "<<now<<" k "<<k<<" :: "<<x<<" "<<y<<endl; //cout<<" infor of now "<<t[now].sz<<" "<<t[now].rev<<" || "<<t[now].val<<" "<<t[t[now].ch[0]].val<<" "<<t[t[now].ch[1]].val<<" "<<t[t[t[now].ch[1]].ch[1]].val<<" || "<<tot<<endl; if(t[now].sz==0){ x=0;y=0;return; } pushdown(now); //cout<<" after pushdown "<<t[now].sz<<" "<<t[now].rev<<" || "<<t[now].val<<" "<<t[t[now].ch[0]].val<<" "<<t[t[now].ch[1]].val<<" "<<t[t[t[now].ch[1]].ch[1]].val<<" || "<<tot<<endl; if(t[t[now].ch[0]].sz+1<=k){ k-=t[t[now].ch[0]].sz+1; x=cpy(now); split(t[now].ch[1],k,t[x].ch[1],y); pushup(x); }else{ // cout<<" cpy y"<<endl; y=cpy(now); split(t[now].ch[0],k,x,t[y].ch[0]); pushup(y); } } int main(){ srand(19260817); rd(n); ll las=0; int st,op; int p;ll a; int l,r; int x,y,z; for(reg i=1;i<=n;++i){ rd(st);rd(op); if(op==1){ scanf("%d%lld",&p,&a); p^=las;a^=las; // cout<<" insert "<<endl; // cout<<" p a "<<p<<" "<<a<<endl; split(rt[st],p,x,y); rt[i]=merge(merge(x,nc(a)),y); }else if(op==2){ scanf("%d",&p); p^=las; split(rt[st],p,x,y); split(x,p-1,x,z); dp[++top]=z; rt[i]=merge(x,y); }else if(op==3){ scanf("%d%d",&l,&r); l^=las;r^=las; // cout<<" reverse "<<endl; // cout<<" l r "<<l<<" "<<r<<endl; split(rt[st],r,x,y); split(x,l-1,x,z); z=cpy(z); t[z].rev^=1; rt[i]=merge(merge(x,z),y); }else { scanf("%d%d",&l,&r); l^=las;r^=las; // cout<<" query "<<endl; /// cout<<" l r "<<l<<" "<<r<<endl; split(rt[st],r,x,y); // cout<<" split 1 "<<endl; split(x,l-1,x,z); // cout<<" split 2 "<<endl; las=t[z].sum; printf("%lld\n",las); rt[i]=merge(merge(x,z),y); } } return 0; } } signed main(){ Miracle::main(); return 0; } /* Author: *Miracle* Date: 2019/1/6 12:20:00 */View Code
總結:
範浩強太巨啦
簡單實用,你值得擁有。