主席树 (模板)

思想:主席树就是一颗持久化线段树, 为什么叫持久化了, 因为它可以保存之前的线段树版本, 并且可以拿来用, 从而优化空间. (至于为什么叫主席树了, 大概是因为发明这个算法的人的名字的缘故吧….)
详细说说:
主席树是一种离线数据结构,是由很多棵线段树组成的。
第i棵线段树存的是前i个数的信息:
每一个线段存数字的出现次数(因此建树之前要离散化)。
那么n棵线段树不会MLE吗? 肯定会了!
但是我们发现第i棵线段树和第i-1棵线段树是非常相似的,有许多结点完全相同,因此可以借用之前的结点,没必要新建结点. 从而优化了空间.

模板题
题意: 求区间第k大.
思路: 建一颗权值主席树, 每次统计出一个区间中出现的次数, 然后对于这个区间我们可以通过判断与k的关系, 从而找出第k大.

const int maxn = 1e5+5;
int n, m;
struct Tree {
    int ls, rs, val; // 左右儿子的编号, 和维护的一个值.
}tree[maxn*40];
int idx = 0, root[maxn];
int build(int l, int r) {
    int nod = ++idx;
    tree[nod].val = 0;
    if (l == r) return nod;
    int mid = (l + r) >> 1;
    tree[nod].ls = build(l, mid);
    tree[nod].rs = build(mid+1, r);
    return nod;
}
int update(int pre, int l, int r, int pos, int val) {
    int nod = ++idx;
    tree[nod] = tree[pre]; tree[nod].val++;
    if (l == r) return nod;
    int mid = (l + r) >> 1;
    if (pos <= mid) tree[nod].ls = update(tree[pre].ls, l, mid, pos, val);
    else tree[nod].rs = update(tree[pre].rs, mid+1, r, pos, val);
    return nod;
}
int query(int ql, int qr, int l, int r, int k) {
    if (l == r) return l;
    int mid = (l + r) >> 1;
    int num = tree[tree[qr].ls].val - tree[tree[ql].ls].val;    //左边的数多少
    if (num >= k) return query(tree[ql].ls, tree[qr].ls, l, mid, k);
    else return query(tree[ql].rs, tree[qr].rs, mid + 1, r, k - num);
}
vector<int>ve;
int getid(int x) {
    return lower_bound(ve.begin(), ve.end(), x) - ve.begin() + 1;
}
int a[maxn];
void solve() {
    scanf("%d%d", &n, &m);
    for (int i = 1 ; i <= n ; i ++) {
        scanf("%d", a+i);
        ve.pb(a[i]);
    }
    sort(ve.begin(), ve.end());
    ve.erase(unique(ve.begin(), ve.end()), ve.end());
    root[0] = build(1, sz(ve));    //根节点
    for (int i = 1 ; i <= n ; i ++) {
        int pos = getid(a[i]);
        root[i] = update(root[i-1], 1, sz(ve), pos, a[i]);
    }
    while (m--) {
        int l, r, k; scanf("%d%d%d", &l, &r, &k);
        printf("%d\n", ve[query(root[l-1], root[r], 1, sz(ve), k)-1]);
    }
}

主席树可以解决的经典问题:
1: 区间第k大上面的代码POJ-2104
2: 区间中不同数的个数 SPOJ-DQUERY
3: 区间中比某个数小的个数(或者和)或者小于某个数的最大的数是多少个数HDU-4417
和的HDU-6162 (不过要套树剖)
4: 维护可持久化数组模板题洛谷 - 3919
5: 既然可以做第4点, 那么就可以做可持久化并查集模板题洛谷 - 3402

你可能感兴趣的:(主席树)