例题
思考
题目给的数据是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;
}