• 权值线段树+动态开点


    例题

    普通平衡树

    思考

    题目给的数据是1e-7到1e7,直接写线段树内存肯定是比较吃力,而且题目还要维护rank和第k大,这时候就用到动态开点了,因为操作数一共就1e5,所以最多也只需要开(log_2(2e7))大小的数组。

    • 维护size数组记录该节点所包含的区间内出现过的数字的节点的个数
    • 维护 ls和rs数组分别记录左右儿子

    动态开点+插入(删除)节点

    • 节点存在代表该节点表示的数存在
    • 节点不存在代表该节点表示的数不存在

    修改函数

    void add(int &rt, int l, int r, int x, int v) {  //修改,注意rt是引用,因为下面要进行修改
        if (rt == 0)
            rt = ++nodecnt;//没有节点,构建该节点
        size[rt] += v;  // v->+ 加点,v->- 删点,v取值为{-1,1},-1则该节点的size--,+1则该节点size++
        if (l == r)
            return;  //建点到目标位置,结束
        int mid = (l + r) >> 1;
        if (x <= mid) add(ls[rt], l, mid, x, v);//在左边
        else add(rs[rt], mid + 1, r, x, v);//在右边
    }
    

    主函数中

     if (op == 1) {
           add(root, -1e9, 1e9, x, 1);  //添加节点
     }
     if (op == 2) {
           add(root, -1e9, 1e9, x, -1);  //删去节点
     }
    

    查询x的排名

    即为x前面存在的节点个数加一

    查询函数

    int getnum(int rt, int l, int r, int x) {  //求x前有多少数
        if (size[rt] == 0 || x > r) {  // x>r时,直接返回当前节点下的节点数目
            return size[rt];
        }
        int mid = (l + r) >> 1;
        if (x <= mid)
            return getnum(ls[rt], l, mid, x);                 //直接在左边节点跑就ok
        return size[ls[rt]] + getnum(rs[rt], mid + 1, r, x);  //左侧的节点数目加上右侧x前的数目
    }
    
    

    主函数中

     if (op == 3) {
           printf("%d
    ", getnum(root, -1e9, 1e9, x) + 1);  //求x的排名
     }
    
    

    查询第k个数

    对当前的k值与左区间的size比较

    • 小于则k在左侧,直接递归左儿子
    • 大于则k在右侧,k减去做儿子的节点数,递归右儿子

    查询函数

    int getk(int rt, int l, int r, int k) {  //求第k大的数
        if (l == r)
            return l;  //查找到k第节点,直接返回
        int mid = (l + r) >> 1;
        if (size[ls[rt]] >= k)
            return getk(ls[rt], l, mid, k);                 //左侧节点树大于k,直接跑左侧
        return getk(rs[rt], mid + 1, r, k - size[ls[rt]]);  //左侧节点数小于k,减去左侧节点数,跑右侧
    }
    

    主函数

    if (op == 4) {
          printf("%d
    ", getk(root, -1e9, 1e9, x));  //求第x大的数字
    }
    

    前趋和后继

    • 前趋,先求x前有多少个数,假设为num,那么第num个就是x的前趋
    • 后继,求x+1前有多少个数,假设为num,那么第num+1个数为x的后继
    if (op == 5) {
          printf("%d
    ", getk(root, -1e9, 1e9, getnum(root, -1e9, 1e9, x)));
    }
    if (op == 6) {
          printf("%d
    ", getk(root, -1e9, 1e9, getnum(root, -1e9, 1e9, x + 1) + 1));
    }
    

    例题代码

    #include <bits/stdc++.h>
    using namespace std;
    const int maxn = 1e5 + 10;
    const int logm = 30;
    int root;
    int nodecnt;
    int ls[maxn * logm], rs[maxn * logm], size[maxn * logm];
    void add(int &rt, int l, int r, int x, int v) {//修改
        if (rt == 0)
            rt = ++nodecnt;
        size[rt] += v;// v->+ 加点,v->- 删点
        if (l == r)
            return;//建点到目标位置,结束
        int mid = (l + r) >> 1;
        if (x <= mid)
            add(ls[rt], l, mid, x, v);
        else
            add(rs[rt], mid + 1, r, x, v);
    }
    int getnum(int rt, int l, int r, int x) {//求x前有多少数
        if (size[rt] == 0 || x > r) {// x>r时,直接返回当前节点下的节点数目
            return size[rt];
        }
        int mid = (l + r) >> 1;
        if (x <= mid)
            return getnum(ls[rt], l, mid, x);//直接在左边节点跑就ok
        return size[ls[rt]] + getnum(rs[rt], mid + 1, r, x);//左侧的节点数目加上右侧x前的数目
    }
    int getk(int rt, int l, int r, int k) {  //求第k大的数
        if (l == r)
            return l;//查找到k第节点,直接返回
        int mid = (l + r) >> 1;
        if (size[ls[rt]] >= k)
            return getk(ls[rt], l, mid, k);//左侧节点树大于k,直接跑左侧
        return getk(rs[rt], mid + 1, r, k - size[ls[rt]]);//左侧节点数小于k,减去左侧节点数,跑右侧
    }
    int main() {
        int n;
        scanf("%d", &n);
        int op, x;
        for (int i = 1; i <= n; i++) {
            scanf("%d%d", &op, &x);
            if (op == 1) {
                add(root, -1e9, 1e9, x, 1);//添加节点
            }
            if (op == 2) {
                add(root, -1e9, 1e9, x, -1);//删去节点
            }
            if (op == 3) {
                printf("%d
    ", getnum(root, -1e9, 1e9, x) + 1);//求x的排名
            }
            if (op == 4) {
                printf("%d
    ", getk(root, -1e9, 1e9, x));//求第x大的数字
            }
            if (op == 5) {
                printf("%d
    ", getk(root, -1e9, 1e9, getnum(root, -1e9, 1e9, x)));
                /*前趋,先求x前有多少个数,假设为num,那么第num个就是x的前趋*/
            }
            if (op == 6) {
                printf("%d
    ", getk(root, -1e9, 1e9, getnum(root, -1e9, 1e9, x + 1) + 1));
                /*后继,求x+1前有多少个数,假设为num,那么第num+1个数为x的后继*/
            }
        }
        return 0;
    }
    
    

    完结

  • 相关阅读:
    SpringMVC是什么?
    SpringMVC工作原理
    SQL给字段加上统一的某个字符
    把数据库里的标签去掉
    Windows通过DOS命令进入MYSQL的方法
    mysql添加字段
    sqlserver查询最接近的记录
    LIST 排序
    Tsk4.5异步
    认识和使用Task
  • 原文地址:https://www.cnblogs.com/soda-ma/p/13346670.html
Copyright © 2020-2023  润新知