• 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;
    }
  • 相关阅读:
    Semaphore
    财报分析
    关于C#中的new的用法
    Linux(CentOS)下Postgresql数据库的安装配置
    CentOS下实现SCP免输密码传送文件
    HiveQL逻辑执行顺序
    CentOS上以源码的方式安装Redis笔记
    Python学习心得(七) 深入理解threading多线程模块
    SQL Server返回两个Date日期相差共多少天零多少小时零多少分钟零多少秒
    Python学习心得(六) 反射机制、装饰器
  • 原文地址:https://www.cnblogs.com/QingyuYYYYY/p/12309498.html
Copyright © 2020-2023  润新知