权值线段树+动态开点

例题

普通平衡树

思考

题目给的数据是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\n", 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\n", getk(root, -1e9, 1e9, x));  //求第x大的数字
}

前趋和后继

  • 前趋,先求x前有多少个数,假设为num,那么第num个就是x的前趋
  • 后继,求x+1前有多少个数,假设为num,那么第num+1个数为x的后继
if (op == 5) {
      printf("%d\n", getk(root, -1e9, 1e9, getnum(root, -1e9, 1e9, x)));
}
if (op == 6) {
      printf("%d\n", 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\n", getnum(root, -1e9, 1e9, x) + 1);//求x的排名
        }
        if (op == 4) {
            printf("%d\n", getk(root, -1e9, 1e9, x));//求第x大的数字
        }
        if (op == 5) {
            printf("%d\n", getk(root, -1e9, 1e9, getnum(root, -1e9, 1e9, x)));
            /*前趋,先求x前有多少个数,假设为num,那么第num个就是x的前趋*/
        }
        if (op == 6) {
            printf("%d\n", getk(root, -1e9, 1e9, getnum(root, -1e9, 1e9, x + 1) + 1));
            /*后继,求x+1前有多少个数,假设为num,那么第num+1个数为x的后继*/
        }
    }
    return 0;
}

完结

posted @ 2020-07-20 18:40  sodak  阅读(216)  评论(1编辑  收藏  举报