What is the chairman tree? Talking about Chairman Tree

Chairman tree

~

If you have any questions, please discuss them together in the comment section (๑•̀ㅂ•́)و✧

Problem introduction

n numbers, m queries.
Query method: l, r, k.
Find the k-th largest number in the interval [l, r]

Use the chairman tree

In order to solve this problem efficiently, we need to use the chair tree.

So what is the chairman tree?
In a nutshell: a segment tree with historical versions.
The so-called historical version is what the line tree looks like before inserting a certain number.

If n (number of numbers) line segment trees are used to implement this "historical version", memory will be blown up.
However, it is necessary to practice in the process of thinking.
After a little simulation, it can be found that almost half of the nodes in the two adjacent subtrees have not changed, which greatly wastes space. Therefore, for these nodes, we no longer create new ones every time, but directly use the original nodes.
The chairman tree is like this.

Prerequisite skills

As mentioned above, we need to build n line segment trees, but most unchanged nodes will not be newly created.
Then we need to know the scope of the data in advance. What if the range of data is super large? In order to avoid this situation MLE, we need to use discretized knowledge (very simple, don't fall back!).
Discretization link: Discretization
In addition, since there are n line segment trees, you must have learned the line segment tree~
Line segment tree link: line segment tree

Variables to be used

int nodeNum;
// 当前节点的全局编号
int L[N << 5], R[N << 5], sum[N << 5];
// L[i]  : 节点i的左子树的根节点的全局编号
// R[i]  : 节点i的右子树的根节点的全局编号
// sum[i]: 以节点i为根节点的树所储存的数字的数量
int a[N], b[N];
// a: 初始数组
// b: 初始数组的副本,用于离散化
int T[N];
// T[i]: 第i+1棵子树的根节点

Contribute

// 建树, 返回根节点
int build(int l, int r) {
    // num 为当前树的根节点编号
    int num = ++nodeNum;
    if (l != r) {
        int mid = (l + r) >> 1;
        // 建立左子树
        L[num] = build(l, mid); // 当前num节点的左子树的根节点编号
        // 建立右子树
        R[num] = build(mid + 1, r);
    }
    return num;
}

Insert node

// 插入节点x,返回所生成的线段树的根节点
// pre为上一棵子树的根节点
int udNode(int pre, int l, int r, int x) {
    int num = ++nodeNum;
    // 将上一棵子树的左右子树连接到新树的根节点
    L[num] = L[pre];
    R[num] = R[pre];
    // 新树中插入了一个数字,所以sum[num]比sum[pre]大 1
    sum[num] = sum[pre] + 1;
    if (l != r) {
        int mid = (l + r) >> 1;
        // 确定待插入的节点所在的子树
        // 生成新的左/右子树并更新根节点的对应的子树编号
        if (x <= mid) L[num] = udNode(L[pre], l, mid, x);
        else R[num] = udNode(R[pre], mid + 1, r, x);
    }
    return num;
}

Interval query

// 查询[l,r]中第k大/小的数字并返回该数字
int query(int u, int v, int l, int r, int k) {
    // 递归出口:到达叶子节点
    if (l == r) return b[l];
    int mid = (l + r) >> 1;
    // 设 u: l-1, v: r
    // 则 num: 第r+1棵树的左子树的数字的数量减去第l棵树的左子树的数字的数量所得的数字的数量
    int num = sum[L[v]] - sum[L[u]];
    // 若 k 小于等于 num,说明所求数字在左子树
    // 否则说明所求数字在右子树
    if (num >= k) return query(L[u], L[v], l, mid, k);
    else return query(R[u], R[v], mid + 1, r, k - num); 
    // 在右子树时,需要减去左子树的数字的数量。因为全局的第k大/小的数字,在右子树中为第k-num大/小的数字
}

Code for template questions (comments are very useful!)

Topic link: LG template question

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<cstdlib>
#include<string>
#include<queue>
#include<map>
#include<stack>
#include<list>
#include<set>
#include<deque>
#include<vector>
#include<ctime>

using namespace std;
//#pragma GCC optimize(2)
#define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
#define ull unsigned long long
#define ll long long
#define rep(i, x, y) for(int i=x;i<=y;i++)
#define mms(x, n) memset(x, n, sizeof(x))
#define mmc(A, b) memcpy(A, b, sizeof(b))
#define INF (0x3f3f3f3f)
#define mod (ull)(1e9+7)
typedef pair<int, int> P;
const int N = 2e5 + 10;
int nodeNum;
// 当前节点的全局编号
int L[N << 5], R[N << 5], sum[N << 5];
// L[i]  : 节点i的左子树的根节点的全局编号
// R[i]  : 节点i的右子树的根节点的全局编号
// sum[i]: 以节点i为根节点的树所储存的数字的数量
int a[N], b[N];
// a: 初始数组
// b: 初始数组的副本,用于离散化
int T[N];
// T[i]: 第i+1棵子树的根节点

// 建树, 返回根节点
int build(int l, int r) {
    // num 为当前树的根节点编号
    int num = ++nodeNum;
    if (l != r) {
        int mid = (l + r) >> 1;
        // 建立左子树
        L[num] = build(l, mid); // 当前num节点的左子树的根节点编号
        // 建立右子树
        R[num] = build(mid + 1, r);
    }
    return num;
}

// 插入节点x,返回所生成的线段树的根节点
// pre为上一棵子树的根节点
int udNode(int pre, int l, int r, int x) {
    int num = ++nodeNum;
    // 将上一棵子树的左右子树连接到新树的根节点
    L[num] = L[pre];
    R[num] = R[pre];
    // 新树中插入了一个数字,所以sum[num]比sum[pre]大 1
    sum[num] = sum[pre] + 1;
    if (l != r) {
        int mid = (l + r) >> 1;
        // 确定待插入的节点所在的子树
        // 生成新的左/右子树并更新根节点的对应的子树编号
        if (x <= mid) L[num] = udNode(L[pre], l, mid, x);
        else R[num] = udNode(R[pre], mid + 1, r, x);
    }
    return num;
}

// 查询[l,r]中第k大/小的数字并返回该数字
int query(int u, int v, int l, int r, int k) {
    // 递归出口:到达叶子节点
    if (l == r) return b[l];
    int mid = (l + r) >> 1;
    // 设 u: l-1, v: r
    // 则 num: 第r+1棵树的左子树的数字的数量减去第l棵树的左子树的数字的数量所得的数字的数量
    int num = sum[L[v]] - sum[L[u]];
    // 若 k 小于等于 num,说明所求数字在左子树
    // 否则说明所求数字在右子树
    if (num >= k) return query(L[u], L[v], l, mid, k);
    else return query(R[u], R[v], mid + 1, r, k - num); 
    // 在右子树时,需要减去左子树的数字的数量。因为全局的第k大/小的数字,在右子树中为第k-num大/小的数字
}

int main() {
//    freopen("input.txt", "r", stdin);
    int n, m;
    scanf("%d%d", &n, &m);

    // 离散化
    rep(i, 1, n) {
        scanf("%d", &a[i]);
        b[i] = a[i]; // b 数组为 a 数组的副本
    }
    // 排序后去重
    sort(b + 1, b + 1 + n);
    // 获取去重后的数组大小
    int size = unique(b + 1, b + 1 + n) - b - 1;
    // T[0] 为第一棵线段树(空树)的根节点
    T[0] = build(1, size);
    for (int i = 1; i <= n; i++) {
        // 获取映射值
        int x = lower_bound(b + 1, b + 1 + size, a[i]) - b;
        // T[i] 为第i+1棵线段树的根节点
        T[i] = udNode(T[i - 1], 1, size, x);
    }

    // 查询
    while (m--) {
        int l, r, k;
        // [l, r]区间中第k大/小的数字
        scanf("%d%d%d", &l, &r, &k);
        printf("%d\n", query(T[l - 1], T[r], 1, size, k));
    }
    return 0;
}

Guess you like

Origin blog.csdn.net/qq_45934120/article/details/108028914