[总结] fhq_Treap 学习笔记

转自

无旋版 $Treap$。

只需要两个操作即可达到 $splay$ 的所有功能

1、$split$

它的主要思想就是把一个 $Treap$ 分成两个。

 $split$ 操作有两种类型,一种是按照权值分配,一种是按前 k 个分配。

第一种就是把所有小于 k 的权值的节点分到一棵树中,第二种是把前 k 个分到一个树里。

权值版:

 1 void split(int o,int k,int &x,int &y){ //这里的x,y分别是将以o为根的树切开后第一个新子树的根和第二个新子树的根
 2     if(!o) x=y=0;
 3     else {
 4         if(val[o]<=k)
 5             x=o,split(ch[o][1],k,ch[o][1],y);
 6         else 
 7             y=o,split(ch[o][0],k,x,ch[o][0]);
 8         pushup(o);
 9     }
10 }

对于我们遍历到每一个点,假如它的权值小于k,那么它的所有左子树,都要分到左边的树里,然后遍历它的右儿子。假如大于k,把它的所有右子树分到右边的树里,遍历左儿子。

因为它的最多操作次数就是一直分到底,效率就是 $O(logn)$。

 

对于前k个版的,就是像找第k大的感觉。每次减掉sze

void split(int now,int k,int &x,int &y){
    if (!now) x=y=0;
    else{
        if (k<=siz[ch[now][0]])
            y=now,split(ch[now][0],k,x,ch[now][0]);
        else
            x=now,split(ch[now][1],k-sze[ch[now][0]]-1,ch[now][1],y);
        pushup(now);
    }
}

2、$merge$

这个就是把两个 $Treap$ 合成一个,保证第一个的权值小于第二个。

因为第一个 $Treap$ 的权值都比较小,我们比较一下它的 $prio$ (优先级),假如第一个的 $prio$ 小,我们就可以直接保留它的所有左子树,接着把第一个 $Treap$ 变成它的右儿子。反之,我们可以保留第二棵的所有右子树,指针指向左儿子。

你可以把这个过程形象的理解为在第一个 $ Treap$ 的右子树上插入第二个树,也可以理解为在第二个树的左子树上插入第一棵树。因为第一棵树都满足小于第二个树,所以就变成了比较 $prio$ 来确定树的形态。

也就是说,我们其实是遍历了第一个$Treap$ 的根->最大节点,第二个$Treap$的根->最小节点,也就是 $O(logn)$

int merge(int x,int y){
    if(!x or !y) return x+y;
    if(prio[x]<prio[y]){
        ch[x][1]=merge(ch[x][1],y);
        pushup(x);
        return x;
    }
    else{
        ch[y][0]=merge(x,ch[y][0]);
        pushup(y);
        return y;
    }
}

下面我们就可以通过这两个基本的东西实现各种各样的操作了。

3、insert

插入一个权值为 $k$ 的点,把树按照 $k$ 的权值 $split$ 成两个,再 $merge$ 回去。

4、remove

删除权值为 $k$ 的点,把树按照 $k$ 分成两个$a,b$ 再把$a$ 按照 $k-1$ 分成$c,d$。把$d$ 的两个儿子 $merge$起来,再 $merge(merge(c,d),b)$

void remove(int k){
    int x,y,z;
    split(Root,k,x,y);
    split(x,k-1,x,z);
    z=merge(ch[z][0],ch[z][1]);
    Root=merge(x,merge(z,y));
}

其它见代码

// 普通平衡树 fhq_Treap
// By YoungNeal
#include<cstdio>
#include<cstdlib>
#define N 100005
#define inf 0x3f3f3f3f

int Root;
int n,opt,x,tot;
int val[N],prio[N];
int sze[N],ch[N][2];

void pushup(int o){
    sze[o]=sze[ch[o][0]]+sze[ch[o][1]]+1;
}

void split(int o,int k,int &x,int &y){
    if(!o) x=y=0;
    else {
        if(val[o]<=k)
            x=o,split(ch[o][1],k,ch[o][1],y);
        else 
            y=o,split(ch[o][0],k,x,ch[o][0]);
        pushup(o);
    }
}

int merge(int x,int y){
    if(!x or !y) return x+y;
    if(prio[x]<prio[y]){
        ch[x][1]=merge(ch[x][1],y);
        pushup(x);
        return x;
    }
    else{
        ch[y][0]=merge(x,ch[y][0]);
        pushup(y);
        return y;
    }
}

int newnode(int v){
    sze[++tot]=1;
    val[tot]=v;
    prio[tot]=rand();
    return tot;
}

void insert(int k){
    int x,y;
    split(Root,k,x,y);
    Root=merge(merge(x,newnode(k)),y);
}

void remove(int k){
    int x,y,z;
    split(Root,k,x,y);
    split(x,k-1,x,z);
    z=merge(ch[z][0],ch[z][1]);
    Root=merge(x,merge(z,y));
}

void kthrank(int k){
    int x,y;
    split(Root,k-1,x,y);
    printf("%d\n",sze[x]+1);
    Root=merge(x,y);
}

int rank(int o,int k){
    if(sze[ch[o][0]]==k-1) return val[o];
    if(sze[ch[o][0]]>=k) return rank(ch[o][0],k);
    return rank(ch[o][1],k-sze[ch[o][0]]-1);
}

void prev(int k){
    int x,y;
    split(Root,k-1,x,y);
    printf("%d\n",rank(x,sze[x]));
    Root=merge(x,y);
}

void nxt(int k){
    int x,y;
    split(Root,k,x,y);
    printf("%d\n",rank(y,1));
    Root=merge(x,y);
}

signed main(){
    scanf("%d",&n);
    while(n--){
        scanf("%d%d",&opt,&x);
        if(opt==1) insert(x);
        if(opt==2) remove(x);
        if(opt==3) kthrank(x);
        if(opt==4) printf("%d\n",rank(Root,x));
        if(opt==5) prev(x);
        if(opt==6) nxt(x);
    }
    return 0;
}

5、区间操作

对于翻转区间 $[l,r]$,我们可以先把区间 $[1,l-1]$ $split$ 出来,再把 $[l,r]$ $split$ 出来就行了。注意 $lazy$ 标记及时清除。

// 文艺平衡树 fhp_Treap
// By YoungNeal
#include<ctime>
#include<cstdio>
#include<cstdlib>
#define N 100005

int Root;
int lazy[N];
int n,m,cnt;
int val[N],sze[N];
int ch[N][2],prio[N];

void pushup(int o){
    sze[o]=sze[ch[o][0]]+sze[ch[o][1]]+1;
}

void pushdown(int o){
    if(!lazy[o] or !o) return;
    ch[o][0]^=ch[o][1]^=ch[o][0]^=ch[o][1];
    lazy[ch[o][0]]^=1;
    lazy[ch[o][1]]^=1;
    lazy[o]=0;
}

void split(int o,int k,int &x,int &y){
    if(!o) x=y=0;
    else{
        pushdown(o);
        if(k>sze[ch[o][0]]) x=o,split(ch[o][1],k-sze[ch[o][0]]-1,ch[o][1],y);
        else y=o,split(ch[o][0],k,x,ch[o][0]);
        pushup(o);
    }
}

int merge(int x,int y){
    if(!x or !y) return x+y;
    pushdown(x); pushdown(y);
    if(prio[x]<prio[y]){
        ch[x][1]=merge(ch[x][1],y);
        pushup(x);
        return x;
    }
    else{
        ch[y][0]=merge(x,ch[y][0]);
        pushup(y);
        return y;
    }
}

int newnode(int v){
    val[++cnt]=v;
    sze[cnt]=1;
    prio[cnt]=rand();
    return cnt;
}

void res(int l,int r){
    int a,b,c,d;
    split(Root,r,a,b);
    split(a,l-1,c,d);
    lazy[d]^=1;
    Root=merge(merge(c,d),b);
}

void dfs(int now){
    if(!now) return;
    pushdown(now);
    dfs(ch[now][0]);
    printf("%d ",val[now]);
    dfs(ch[now][1]);
}

signed main(){
    srand(time(0));
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        Root=merge(Root,newnode(i));
    //printf("Root=%d\n",Root);
    for(int x,y,i=1;i<=m;i++){
        scanf("%d%d",&x,&y);
        res(x,y);
        //printf("i=%d\n",i);
        //dfs(Root);
    }
    //printf("Root=%d\n",Root);
    dfs(Root);
    return 0;
}

 

posted @ 2018-05-01 20:33  YoungNeal  阅读(2085)  评论(3编辑  收藏  举报