treap学习笔记

推荐一篇洛谷日报 讲的超级清晰/bx

1.treap

首先 treap=tree+heap

即 二叉搜索树+堆

二叉搜索树:指根的左儿子比根小,右儿子比根大的二叉树

显然它有一个性质:它的中序遍历一定是一个有序序列

但是还有一个问题:树可能退化成一条链(单次复杂度\(O(\log n)\)-->\(O(n)\)

那么我们就可以给每个点取随机数 再用堆的方式维护

这样做就能让深度更平均 防止复杂度假掉

2.操作

1.维护信息:

int siz[N],num[N],rd[N],v[N],son[N][2];

siz[k]:k点的子树大小

num[k]:k点的存的数字的数量

rd[k]:k点的随机数(用于堆排序)

v[k]:k点维护的值

son[k][0/1]:k点的左/右儿子

2.pushup

inl void pushup(int k){siz[k]=siz[son[k][0]]+siz[son[k][1]]+num[k];}

同线段树 合并子树信息

3.旋转

//d=0 左旋 d=1 右旋
inl void rotate(int &k,int d){
    int p=son[k][d^1];
    son[k][d^1]=son[p][d];
    son[p][d]=k;
    pushup(k);
    pushup(p);
    k=p;
}

大致操作就是 如果右旋 就把父节点A接到左儿子B下面 再把B的右儿子变成A的左儿子 左旋同理
image
贺张巨佬的图

并且观察一下我们发现 rotate操作前后树的中序遍历相同

也就是我们可以用它在不影响其搜索树性质情况下 维护其堆的性质

4.插入

void ins(int &k,int x){
    if(!k){
        k=++cnt;num[k]=siz[k]=1;
        v[k]=x;rd[k]=rand();
        return;
    }
    if(x==v[k]){num[k]++;siz[k]++;return;}
    int d=(x<v[k]);
    ins(son[k][d^1],x);
    if(rd[k]<rd[son[k][d^1]])
        rotate(k,d);
    pushup(k);
}

1.当前为空结点:直接添加信息并返回

2.当前节点值与x一致:num、siz++

3.否则根据大小向左右子树递归 最后记得旋转+pushup

5.删除

void del(int &k,int x){
    if(!k)return;
    if(x<v[k])del(son[k][0],x);
    else if(x>v[k])del(son[k][1],x);
    else{
        if(num[k]>1){
            num[k]--;siz[k]--;return;
        }else if(!son[k][0]&&!son[k][1]){
            num[k]--;siz[k]--;k=0;
        }else if(son[k][0]&&!son[k][1]){
            rotate(k,1);
            del(son[k][1],x);
        }else if(!son[k][0]&&son[k][1]){
            rotate(k,0);
            del(son[k][0],x);
        }else if(son[k][0]&&son[k][1]){
            int d=(rd[son[k][0]]<rd[son[k][1]]);
            rotate(k,d^1);del(son[k][d^1],x);
        }
    }
    pushup(k);
}

1.访问到空节点:没找到 直接返回

2.如果当前节点值与x不同 向左右子树递归

3.当前节点值与x相同且数量>1:直接删除一个并返回

4.否则节点值与x相同且数量=1:将该节点旋转到叶子结点再删除 具体细节看代码

6.查询排名

int rk(int k,int x){
    if(!k)return 1;
    if(x==v[k])return siz[son[k][0]]+1;
    if(x<v[k])return rk(son[k][0],x);
    return siz[son[k][0]]+num[k]+rk(son[k][1],x);
}

1.查询到空结点:只会在x不在树中时出现 此时排名要加1(调了一个小时/fn)

2.当前节点值等于x:显然排名为左子树大小+1

3.x比当前点值小:递归左树

4.否则x比当前点值大:左子树大小+当前点数量+右子树排名(应该很好理解吧)

7.查询第x大的值

int find(int k,int x){
    if(!k)return 0;
    if(x<=siz[son[k][0]])
        return find(son[k][0],x);
    if(x>siz[son[k][0]]+num[k])
        return find(son[k][1],x-siz[son[k][0]]-num[k]);
    return v[k];
}

这个操作比较类似于主席树

1.查询到空结点:没找到 返回

2.x小于左子树大小:递归左树

3.x比左子树+当前点大小还大:递归右树 找第x-siz[左子树]-num[当前点]个

4.否则第x个就是当前节点 返回当前节点值

8.前驱/后继

这俩差不多 放一起了

先看前驱(前驱定义为小于x,且最大的数):

int pre(int k,int x){
    if(!k)return -inf;
    if(v[k]>=x)return pre(son[k][0],x);
    return max(v[k],pre(son[k][1],x));
}

1.找到空节点:没有前驱 返回负无穷

2.当前点等于x或比x大:显然不符合要求 左子树递归找更小的

3.否则当前点值小于x:符合要求 在当前点和右子树中找最小值

后继一样:

int suc(int k,int x){
    if(!k)return inf;
    if(v[k]<=x)return suc(son[k][1],x);
    return min(v[k],suc(son[k][0],x));
}

最后是完整代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inl inline 
#define gc getchar
#define pc putchar
const int N=5e5+5;
const int M=1e5+5;
const int inf=INT_MAX;
const int mod=9901;
inl int read(){
    int x=0,f=1;char c=gc();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=gc();}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=gc();}
    return x*f;
}
inl void write(int x){
    if(x<0){pc('-');x=-x;}
    if(x>9)write(x/10);
    pc(x%10+'0');
}
inl void writel(int x){write(x);pc('\n');}
int n,cnt,rt,op,x;
struct treap{
    int siz[N],num[N],rd[N],v[N],son[N][2];
    inl void pushup(int k){siz[k]=siz[son[k][0]]+siz[son[k][1]]+num[k];}
    inl void rotate(int &k,int d){
        int p=son[k][d^1];
        son[k][d^1]=son[p][d];
        son[p][d]=k;
        pushup(k);
        pushup(p);
        k=p;
    }
    void ins(int &k,int x){
        if(!k){
            k=++cnt;num[k]=siz[k]=1;
            v[k]=x;rd[k]=rand();
            return;
        }
        if(x==v[k]){num[k]++;siz[k]++;return;}
        int d=(x<v[k]);
        ins(son[k][d^1],x);
        if(rd[k]<rd[son[k][d^1]])
            rotate(k,d);
        pushup(k);
    }
    void del(int &k,int x){
        if(!k)return;
        if(x<v[k])del(son[k][0],x);
        else if(x>v[k])del(son[k][1],x);
        else{
            if(num[k]>1){
                num[k]--;siz[k]--;
                return;
            }else if(!son[k][0]&&!son[k][1]){
                num[k]--;siz[k]--;k=0;
            }else if(son[k][0]&&!son[k][1]){
                rotate(k,1);
                del(son[k][1],x);
            }else if(!son[k][0]&&son[k][1]){
                rotate(k,0);
                del(son[k][0],x);
            }else if(son[k][0]&&son[k][1]){
                int d=(rd[son[k][0]]<rd[son[k][1]]);
                rotate(k,d^1);del(son[k][d^1],x);
            }
        }
        pushup(k);
    }
    int rk(int k,int x){
        if(!k)return 1;
        if(x==v[k])return siz[son[k][0]]+1;
        if(x<v[k])return rk(son[k][0],x);
        return siz[son[k][0]]+num[k]+rk(son[k][1],x);
    }
    int find(int k,int x){
        if(!k)return 1;
        if(x<=siz[son[k][0]])
            return find(son[k][0],x);
        if(x>siz[son[k][0]]+num[k])
            return find(son[k][1],x-siz[son[k][0]]-num[k]);
        return v[k];
    }
    int pre(int k,int x){
        if(!k)return -inf;
        if(v[k]>=x)return pre(son[k][0],x);
        return max(v[k],pre(son[k][1],x));
    }
    int suc(int k,int x){
        if(!k)return inf;
        if(v[k]<=x)return suc(son[k][1],x);
        return min(v[k],suc(son[k][0],x));
    }
}tree;
signed main(){
    n=read();
    while(n--){
        op=read();x=read();
        if(op==1)tree.ins(rt,x);
        if(op==2)tree.del(rt,x);
        if(op==3)writel(tree.rk(rt,x));
        if(op==4)writel(tree.find(rt,x));
        if(op==5)writel(tree.pre(rt,x));
        if(op==6)writel(tree.suc(rt,x));
    }
    return 0;
}
posted @ 2023-09-26 11:50  xiang_xiang  阅读(16)  评论(0编辑  收藏  举报