主席树

主席树是一种可持久化线段树、其发明者orz 黄嘉泰 拼音缩写与某届主席一样、于是这个数据结构被戏称为主席树。

所谓的“持久化数据结构”、就是保存这个数据结构的所有历史版本、同时利用它们之间的共用数据减少时间和空间的消耗。

由于线段树在区间长度固定的情况下结构都是一致的、主席树能够通过两颗线段树相减来得到某一区间的信息。

至于主席树的作用、其能够查询不修改的区间K大值、区间不同数的个数、套个树状数组还能查询动态K大值......

给出几篇文章以便学习 ==> 链接II、链接II、链接III

一些题目 :

HDU 2665 (可作为模板使用)

题意 : 给出一个整数序列、有若干个问询、每次问询 (L, R, K) 表示 L~R 区间内第 K 大的值是多少

分析 : 比较裸的主席树题目

首先先对于每个前缀按权值建出主席树、然后问询的时候就可以通过减法得到区间 (L, R) 的信息

由于存储的是值域信息、查询K大值的时候就判断左右子区间的元素个数与K的大小便能知道往哪个方向走

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
struct NODE{
    int sum, L, R;
    NODE(){};
    NODE(int _sum, int _L, int _R):
        sum(_sum),L(_L),R(_R){};
}T[maxn*18]; int Tcnt = 0;

int root[maxn];
int arr[maxn], N;
int uni[maxn], uniLen;

int newNode(int sum, int L, int R)
{
    T[++Tcnt] = NODE(sum, L, R);
    return Tcnt;
}

inline void Insert(int &root, int pre, int pos, int L, int R)
{
    root = newNode(T[pre].sum+1, T[pre].L, T[pre].R);
    if(L == R) return ;
    int M = L + ((R-L)>>1);
    if(pos <= M) Insert(T[root].L, T[pre].L, pos, L, M);
    else Insert(T[root].R, T[pre].R, pos, M+1, R);
}

int Kth(int x, int y, int L, int R, int K)
{
    if(L == R) return L;
    int M = L + ((R-L)>>1);
    int L_sum = T[T[y].L].sum - T[T[x].L].sum;
    if(K <= L_sum) Kth(T[x].L, T[y].L, L, M, K);
    else Kth(T[x].R, T[y].R, M+1, R, K - L_sum);
}

int main(void)
{
    int nCase;
    scanf("%d", &nCase);
    while(nCase--){

        T[0] = NODE(0, 0, 0);///将 0 号节点的左右子树指向自己
        Tcnt = root[0] = 0;///便不用显式建树

        int Q;
        scanf("%d %d", &N, &Q);
        for(int i=1; i<=N; i++){
            scanf("%d", &arr[i]);
            uni[i-1] = arr[i];
        }

        sort(uni, uni+N);
        uniLen = unique(uni, uni+N) - uni;///离散化

        for(int i=1; i<=N; i++){
            int pos = lower_bound(uni, uni+uniLen, arr[i]) - uni;
            pos++;
            Insert(root[i], root[i-1], pos, 1, uniLen+1);
        }

        int l, r, k;
        while(Q--){
            scanf("%d %d %d", &l, &r, &k);
            int pos = Kth(root[l-1], root[r], 1, uniLen+1, k);
            printf("%d
", uni[--pos]);
        }
    }
    return 0;
}
View Code

SPOJ D-QUERY

题意 : 给出 n 个整数、每次问询一个区间 (L, R) 问这个区间内不同数的个数是多少?

分析 :

这题很久之前用线段树离线做过 ==> Click here

如果使用主席树的话就能做到在线回答问询

具体做法的核心思路其实和线段树离线的时候差不多

但是这里主席树存储的并不是值域信息、而是区间具体每个位置是否包含一种数

也就是这题主席树区间代表的信息和普通线段树所代表的信息一样

主席树每次按照前缀建树、建树的时候保证每一种数只保留最右边的位置

然后对于问询 (l, r) 只要在 root[r] 这颗树上查询端点 l 右边的所有 sum 值的和即可

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
struct NODE{
    int sum, L, R;
    NODE(){};
    NODE(int _sum, int _L, int _R):
        sum(_sum),L(_L),R(_R){};
}T[maxn*18]; int Tcnt = 0;

int root[maxn];
int arr[maxn], N;

int newNode(int sum, int L, int R)
{
    T[++Tcnt] = NODE(sum, L, R);
    return Tcnt;
}

inline void Insert(int &root, int pre, int pos, int val, int L, int R)
{
    root = newNode(T[pre].sum+val, T[pre].L, T[pre].R);
    if(L == R) return ;
    int M = L + ((R-L)>>1);
    if(pos <= M) Insert(T[root].L, T[pre].L, pos, val, L, M);
    else Insert(T[root].R, T[pre].R, pos, val, M+1, R);
}

int query(int rt, int pos, int L, int R)
{
    if(L == R) return T[rt].sum;
    int ret = 0;
    int M = L + ((R-L)>>1);
    if(pos <= M) ret += query(T[rt].L, pos, L, M) + T[T[rt].R].sum;///递归进入左边区间的时候、要把右边区间的和加上
    else ret += query(T[rt].R, pos, M+1, R);
    return ret;
}

map<int, int> mp;
int main(void)
{
    T[0] = NODE(0, 0, 0);
    root[0] = 0; Tcnt = 0;
    scanf("%d", &N);
    for(int i=1; i<=N; i++) scanf("%d", &arr[i]);
    for(int i=1; i<=N; i++){
        if(mp.count(arr[i])){
            int tmpRoot;
            Insert(tmpRoot, root[i-1], i, 1, 1, N);
            Insert(root[i], tmpRoot, mp[arr[i]], -1, 1, N);
        }else Insert(root[i], root[i-1], i, 1, 1, N);
        mp[arr[i]] = i;
    }

    int Q;
    scanf("%d", &Q);
    while(Q--){
        int l, r;
        scanf("%d %d", &l, &r);
        printf("%d
", query(root[r], l, 1, N));
    }
    return 0;
}
View Code
原文地址:https://www.cnblogs.com/qwertiLH/p/9154722.html