1. 程式人生 > >[學習筆記]FHQ-Treap及其可持久化

[學習筆記]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

 

總結:

範浩強太巨啦

簡單實用,你值得擁有。