¿Qué es el árbol del presidente? Hablando de Chairman Tree

Presidente árbol

~

Si tiene alguna pregunta, discútala juntos en la sección de comentarios (๑ • ̀ ㅂ • ́) و✧

Introducción al problema

n números, m consultas.
Método de consulta: l, r, k.
Encuentra el k-ésimo número más grande en el intervalo [l, r]

Usa el árbol del presidente

Para resolver este problema de manera eficiente, necesitamos usar el árbol de la silla.

Entonces, ¿qué es el árbol del presidente?
En pocas palabras: un árbol de segmentos con versiones históricas.
La llamada versión histórica es el aspecto del árbol de líneas antes de insertar un cierto número.

Si se utilizan n (número de números) árboles de segmentos de línea para implementar esta "versión histórica", la memoria explotará.
Sin embargo, es necesario practicar en el proceso de pensar.
Después de una pequeña simulación, se puede encontrar que casi la mitad de los nodos en los dos subárboles adyacentes no han cambiado, lo que desperdicia mucho espacio. Por lo tanto, para estos nodos, ya no creamos nuevos cada vez, sino que usamos directamente los nodos originales.
El árbol del presidente es así.

Habilidades previas

Como se mencionó anteriormente, necesitamos construir n árboles de segmentos de línea, pero la mayoría de los nodos sin cambios no se crearán nuevamente.
Entonces necesitamos conocer el alcance de los datos de antemano. ¿Qué pasa si el rango de datos es muy grande? Para evitar esta situación MLE, necesitamos usar conocimiento discretizado (muy simple, ¡no retrocedas!).
Enlace de discretización: Discretización
Además, dado que hay n árboles de segmento de línea, debe haber aprendido el árbol de segmento de línea ~
Enlace de árbol de segmento de línea : árbol de segmento de línea

Variables a utilizar

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棵子树的根节点

Contribuir

// 建树, 返回根节点
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;
}

Insertar nodo

// 插入节点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;
}

Consulta de intervalo

// 查询[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大/小的数字
}

Código para preguntas de plantilla (¡los comentarios son muy útiles!)

Enlace de tema: Pregunta de plantilla de LG

#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;
}

Supongo que te gusta

Origin blog.csdn.net/qq_45934120/article/details/108028914
Recomendado
Clasificación