[模板]普通平衡树-Treap版本

//Treap版本平衡树
#include<cstdio>
#include<cstring>
#include<string>
#define WR WinterRain
using namespace std;
const int WR=1001000,INF=1e9+7;
struct BalanceTree{
    int son[2],fa;//son记录左右儿子
    //0是左1是右
    int val,sze,cnt,rnk;
    BalanceTree(){son[0]=son[1]=val=fa=sze=rnk=cnt=0;}
}tree[WR];
int n;
int root,tot;
int read(){
    int s=0,w=1;
    char ch=getchar();
    while(ch>'9'||ch<'0'){
        if(ch=='-') w=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        s=(s<<1)+(s<<3)+ch-48;
        ch=getchar();
    }
    return s*w;
}
int add_point(int v){//新建一个节点
    tree[++tot].val=v;//赋值
    tree[tot].rnk=rand();//随机排名
    tree[tot].sze=1;//大小为1
    tree[tot].cnt=1;//当前节点对应数的个数(副本数)是1
    //比如说有3个大小为1的数,1对应节点的cnt就为3
    return tot;
}
void pushup(int k){
    tree[k].sze=tree[tree[k].son[0]].sze+tree[tree[k].son[1]].sze+tree[k].cnt;
    //本节点子树大小=左儿子子树大小+右儿子子树大小+本节点副本数
}
void build(){
    root=add_point(-INF);
    tree[root].son[1]=add_point(INF);//先加入正无穷和负无穷,便于之后操作
    //因为INF>-INF,所以INF是右子树
    pushup(root);
}
void rotat(int &id,int dir){//dir表示方向,0是左旋1是右旋
    int tmp=tree[id].son[dir^1];//旋转
    tree[id].son[dir^1]=tree[tmp].son[dir];
    tree[tmp].son[dir]=id;
    id=tmp;
    pushup(tree[id].son[dir]);//旋转以后size会改变,只更新自己和转上来的点
    //pushup一下,注意先子节点再父节点
    pushup(id);//旋转实质是在满足BST的性质的基础上比较优先级,
    //然后通过交换本节点和其某个叶子节点把链叉开成二叉形状(从而控制深度)
}
void insrt(int &id,int v){
    if(!id){
        id=add_point(v);
        return;
    }
    if(v==tree[id].val) tree[id].cnt++;//直接++
    else{
        int dir;
        if(v<tree[id].val) dir=0;
        else dir=1;//二叉搜索树,类似于二叉堆
        insrt(tree[id].son[dir],v);//递归建树
        if(tree[id].rnk<tree[tree[id].son[dir]].rnk) rotat(id,dir^1);
        //与左节点交换要右旋,右节点交换左旋
    }
    pushup(id);
}
void remov(int &id,int v){//删除
    if(!id) return;//发现这个节点本来就不存在
    if(v==tree[id].val){
        if(tree[id].cnt>1){
            tree[id].cnt--;
            pushup(id);
            return;//若副本不止一个,减去一个就好
        }
        if(tree[id].son[0]||tree[id].son[1]){
            //发现只有一个值,且有儿子节点,我们只能把值旋转到底部删除
            if(!tree[id].son[1]||tree[tree[id].son[0]].rnk>tree[tree[id].son[1]].rnk){
                //当前点被移走之后,会有一个新的点补上来(左儿子或右儿子)
                //如果没有一边的儿子直接补另一边
                //否则按照优先级,优先级大的补上来
                rotat(id,1);remov(tree[id].son[1],v);
                //我们会发现,右旋是与左儿子交换,当前点变成右节点
            }else{
                rotat(id,0);remov(tree[id].son[0],v);
                //左旋则是与右儿子交换,当前点变为左节点
            }
            pushup(id);
            return;
        }
        id=0;return;//发现这个节点是叶子节点,删掉完事
    }
    if(v<tree[id].val) remov(tree[id].son[0],v);
    else remov(tree[id].son[1],v);
    pushup(id);//继续删除,最后更新
}
int get_rank(int id,int v){
    //printf("%d\n",111);
    if(!id) return 0;
    if(v==tree[id].val) return tree[tree[id].son[0]].sze+1;
    //查询到该值,由BST性质可知:该点左边值都比该点的值(查询值)小,故rank为左儿子大小+1
    if(v<tree[id].val) return get_rank(tree[id].son[0],v);
    //如果小于,递归查询
    else return get_rank(tree[id].son[1],v)+tree[id].cnt+tree[tree[id].son[0]].sze;
    //若查询值大于该点值,说明询问点在当前点的右侧,且此点左子树的值都小于查询值
    //所以要加上cnt[id]和左子树大小
}
int get_val(int id,int rk){
    if(!id) return INF;//向右找找不到说明是正无穷
    if(rk<=tree[tree[id].son[0]].sze) return get_val(tree[id].son[0],rk);
    //根节点排名已经大于rank了,说明rank对应的值在左儿子那里
    else if(rk<=tree[id].cnt+tree[tree[id].son[0]].sze) return tree[id].val;
    //上一步排除了在左区间的情况,若是rank在左与中(目前节点)中,则直接返回目前节点(中区间)的值
    else return get_val(tree[id].son[1],rk-tree[tree[id].son[0]].sze-tree[id].cnt);
    //剩下只能在右区间找了,rank减去左区间大小和根节点大小,继续递归
}
int get_pre(int v){//找前驱
    int id=root,pre;
    while(id){
        if(tree[id].val<v) pre=tree[id].val,id=tree[id].son[1];
        //满足当前节点比目标小,往当前节点的右侧寻找最大值(最小值最大)
        else id=tree[id].son[0];
        //无论是比目标节点大还是等于目标节点,都不满足前驱条件,应往更小处靠近
    }
    return pre;
}
int get_nxt(int v){//找后继
    int id=root,nxt;
    while(id){
        if(tree[id].val>v) nxt=tree[id].val,id=tree[id].son[0];
        //同理,满足条件向左寻找最小值(最大值最小)
        else id=tree[id].son[1];
    }
    return nxt;
}
int main(){
    build();
    n=read();
    for(int i=1;i<=n;i++){
        int opt=read(),x=read();
        if(opt==1) insrt(root,x);
        if(opt==2) remov(root,x);
        if(opt==3) printf("%d\n",get_rank(root,x)-1);
        //注意:因为初始化时插入了INF和-INF,所以查询排名时要减1(-INF不是第一小,是“第零小”)
        if(opt==4) printf("%d\n",get_val(root,x+1));
        if(opt==5) printf("%d\n",get_pre(x));
        if(opt==6) printf("%d\n",get_nxt(x));
    }
    return 0;
}

 

posted @ 2022-05-10 11:05  冬天的雨WR  阅读(22)  评论(0)    收藏  举报
Live2D