luogu P3369 【模板】普通平衡树

————————————————
版权声明:本文为CSDN博主「ModestCoder_」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ModestCoder_/article/details/90139481

清空一个节点
确定一个节点是父亲的左儿子还是右儿子
更新一个节点
把一个点连到另一点下面
上旋
splay
插入一个点
查询一个数的排名
查询排名为k的数
前驱、后继
删除一个点

#include <bits/stdc++.h>
#define maxn 100010
using namespace std;
int sz, rt, f[maxn], key[maxn], size[maxn], recy[maxn], son[maxn][2];

inline int read(){
    int s = 0, w = 1;
    char c = getchar();
    for (; !isdigit(c); c = getchar()) if (c == '-') w = -1;
    for (; isdigit(c); c = getchar()) s = (s << 1) + (s << 3) + (c ^ 48);
    return s * w;
}

void clear(int x){
    f[x] = key[x] = size[x] = recy[x] = son[x][0] = son[x][1] = 0;
    //5个数组全部清零
}

int get(int x){
    return son[f[x]][1] == x;
    //如果自己是父亲的右儿子,返回1;否则返回0(0为左儿子,1为右儿子) 
}

void update(int x){
    if (x){//如果这个点存在
        size[x] = recy[x];//自己重复的次数先累计
        if (son[x][0]) size[x] += size[son[x][0]];
        if (son[x][1]) size[x] += size[son[x][1]];
        //如果存在儿子,把儿子的size累积到自己
        //然后发现一个问题,更新自己的size时,必须保证儿子的size是正确的
        //所以后面的操作,当牵扯到儿子和父亲时,应该先更新新儿子,后更新新父亲
    }
}

void connect(int x, int y, int z){//x连到y下面,关系为z 
    if (x) f[x] = y;//存在x,则x的父亲为y 
    if (y) son[y][z] = x;//存在y,y的z关系儿子为x 
}

void rotate(int x){//上旋x 
    int fa = f[x], ffa = f[fa], m = get(x), n = get(fa);//确定x,fa的关系 
    connect(son[x][m ^ 1], fa, m);//把要转的儿子转到父亲下,关系为m 
    connect(fa, x, m ^ 1);//把父亲转到自己下面,关系为m^1 
    connect(x, ffa, n);//把自己转到父亲的父亲下,关系为n 
    update(fa), update(x);//先更新fa,再更新自己,可以自己想想为什么是这个顺序 
}

void splay(int x){
    for (int fa; fa = f[x]; rotate(x))//每次总是旋转自己 
        if (f[fa]) rotate(get(x) == get(fa) ? fa : x);//如果有爷爷(父亲的父亲),看父亲与父亲的父亲的关系决定转哪个 
    rt = x;//别忘了,把根赋为当前点 
}

void insert(int x){
    if (!rt){//树中没有一个节点 
        rt = ++sz;
        key[rt] = x;
        size[rt] = recy[rt] = 1;
        son[rt][0] = son[rt][1] = 0;//赋初值 
        return;
    }
    int now = rt, fa = 0;
    while (1){
        if (key[now] == x){//树中已有此点,重复+1 
            ++recy[now];
            update(now); update(fa);
            splay(now);//splay一下,保证平衡 
            return;
        }
        fa = now, now = son[now][x > key[now]];//满足二叉查找树的性质,往下跑 
        if (!now){
            ++sz;
            key[sz] = x;
            size[sz] = recy[sz] = 1;//赋初值
            f[sz] = fa;//父亲是fa 
            son[fa][x > key[fa]] = sz;//更新父亲的新儿子 
            update(fa);//更新父亲的size 
            splay(sz);//splay一下,保证平衡
            return;
        }
    }
}

int find(int x){//查排名 
    int now = rt, ans = 0;
    while (1){
        if (x < key[now]){
            now = son[now][0]; continue;//在左子树中 
        }
        ans += size[son[now][0]];//排名加上左子树节点个数 
        if (x == key[now]){ splay(now); return ans + 1; }//值等于当前点,splay一下,保证平衡,排名+1为当前排名 
        ans += recy[now];//排名加上当前节点的数的个数 
        now = son[now][1];//在右子树中 
    }
}

int kth(int x){//查找排名为x的数 
    int now = rt;
    while (1){
        if (son[now][0] && x <= size[son[now][0]]){//在左子树中 
            now = son[now][0]; continue;
        }
        if (son[now][0]) x -= size[son[now][0]];//存在左儿子,排名减去左子树节点数 
        if (x <= recy[now]){ splay(now); return key[now]; }//说明就是当前点,splay一下,保证平衡,退出 
        x -= recy[now];//排名减去当前节点数的个数 
        now = son[now][1];//在右子树中 
    }
}

int pre(){//前驱为左子树中最大的那个 
    int now = son[rt][0];
    while (son[now][1]) now = son[now][1];
    return now;
}

int nxt(){//后继为右子树中最小的那个 
    int now = son[rt][1];
    while (son[now][0]) now = son[now][0];
    return now;
}

void del(int x){
    int no_use = find(x);//find主要是把当前数的对应点找到,然后旋到根,返回值的排名在这里没用 
    if (recy[rt] > 1){//情况1:有重复,重复-1,更新,退出 
        --recy[rt];
        update(rt);
        return;
    }
    //接下来都是没有重复的情况 
    if (!son[rt][0] && !son[rt][1]){//情况2:没有儿子,直接清空 
        clear(rt);
        rt = 0;
         return;
    }
    if (!son[rt][0]){//情况3:没有左儿子,只有右儿子,右儿子变成根,清除自己 
        int tmp = rt;
        f[rt = son[rt][1]] = 0;
        clear(tmp);
        return;
    }
    if (!son[rt][1]){//情况4:没有右儿子,只有左儿子,左儿子变成根,清除自己 
        int tmp = rt;
        f[rt = son[rt][0]] = 0;
        clear(tmp);
        return;
    }
    //情况5:两个儿子都有,这是需要一个很简便的做法
    //把前驱splay到根,保持左子树其他节点不用动
    //原根右儿子变成前驱的右儿子
    //原根功成身退,清除掉
    //最后对前驱的size进行更新 
    int tmp = rt, left = pre();
    splay(left);
    connect(son[tmp][1], rt, 1);
    clear(tmp);
    update(rt);
}

int main(){
    int M = read();
    while (M--){
        int opt = read(), x = read();
        if (opt == 1) insert(x);
        if (opt == 2) del(x);
        if (opt == 3) printf("%d
", find(x));
        if (opt == 4) printf("%d
", kth(x));
        if (opt == 5){
            insert(x); printf("%d
", key[pre()]); del(x);
        }
        if (opt == 6){
            insert(x); printf("%d
", key[nxt()]); del(x);
        }
    }
    return 0;
}
原文地址:https://www.cnblogs.com/QingyuYYYYY/p/12309498.html