[学习笔记]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

 

总结:

范浩强太巨啦

简单实用,你值得拥有。

posted @ 2019-01-06 17:34  *Miracle*  阅读(1347)  评论(0编辑  收藏  举报