主席树

主席树

例题链接

学主席树静态求区间第k大之前,应先学会普通线段树如何求整个序列第k大

建一棵值域线段树,每位的值为当前值有多少个。 然后类似平衡树一样求第k大

主席树是一颗可持久化线段树。 我们发现每次更新一个点的值, 只会改变那个点到根上路径的所有点, 那我们可以动态开点, 重新连一条链出来。

然后利用前缀和的思想, [1~r]的线段树 对应点权值 减去 [1~l-1] 上的权值
就可以得到[l~r]这段区间的信息

然后就可以求区间第k大了

Code

#include<bits/stdc++.h>

using namespace std;

inline int gi() {
    int f = 1, s = 0;
    char c = getchar();
    while (c != '-' && (c < '0' || c > '9')) c = getchar();
    if (c == '-') f = -1, c = getchar();
    while (c >= '0' && c <= '9') s = s*10+c-'0', c = getchar();
    return f == 1 ? s : -s;
}

const int N = 200010;

struct node {
    int v, id;
    bool operator < (node z) const{
        return v < z.v;
    }
}a[N];

int b[N], cnt;

struct tree {
    int lc, rc, v;
}t[N*20];
int root[N], ans[N];

void update(int l, int r, int pos, int &now) {
    t[++cnt] = t[now];
    now = cnt;//这里的now带的是指针, 往下更新时要把它指向更新的儿子
    t[now].v++;
    if (l == r) return ;
    int mid = (l + r) >> 1;
    if (pos <= mid)
        update(l, mid, pos, t[now].lc);
    else update(mid+1, r, pos, t[now].rc);
    return ;
}

int query(int l, int r, int rt1, int rt2, int k) {
    if (l == r) return l;
    int s = t[t[rt2].lc].v - t[t[rt1].lc].v, mid = (l + r) >> 1;
    if (s >= k)
        return query(l, mid, t[rt1].lc, t[rt2].lc, k);
    else return query(mid+1, r, t[rt1].rc, t[rt2].rc, k - s);
}

int main() {
    int n = gi(), m = gi();
    for (int i = 1; i <= n; i++) {
        a[i].v = gi(); a[i].id = i;
    }
    sort(a+1, a+1+n);
    int v = 1;
    b[a[1].id] = v; ans[v] = a[1].v;
    for (int i = 2; i <= n; i++) {
        if (a[i].v != a[i-1].v) v++;
        b[a[i].id] = v; ans[v] = a[i].v;
    }
    for (int i = 1; i <= n; i++) {
        root[i] = root[i-1];
        update(1, n, b[i], root[i]);
    }
    while (m--) {
        int l = gi(), r = gi(), k = gi();
        printf("%d
", ans[query(1, n, root[l-1], root[r], k)]);
    }
    return 0;
}

原文地址:https://www.cnblogs.com/zzy2005/p/10135076.html