主席树(可持久化线段树 )

给一个长为n的序列,m次询问,每次询问[l, r]内第k大的数是几。 n <= 100000, m <= 5000

比如有一个数组n个数据,那么这里记录的是从左往右,每个数据对应的线段树。最后的效果是两个数据的线段树对应值的差值。
而每个数据点(排序去重后所在坐标位置)对一个的线段树记录的是当前对应区间的个数sum(i) (i是区间标号)
举个栗子:
7 1
1 5 2 6 3 7 4
2 5 3
ans=5

update是将这个数据所在的数组位置(不是真值的数据大小排序,所以这里需要一个转换操作)向上走的每个位置sum都+1
用一个新数组记录原数组数据从小到大无重复的数据,然后遍历原数组的时就只要lowerbound查找此数据值对应的位置即可

查询区间[x,y]第k大数的操作为:
1.先抓取x-1与y这两点,两者对应线段树值dt=sum(y)-sum(x-1)作差
2.若k>dt,则说明对应值在右子树中;反之,若k<=dt,则说明在左子树中。(此环节用递归实现即可)

上代码:

const int N = 200010;
int n, q, m, cnt = 0;
int a[N], b[N], T[N];
int sum[N<<5], L[N<<5], R[N<<5];

inline int build(int l, int r)
{
    int rt = ++ cnt;
    sum[rt] = 0;
    if (l < r){
        L[rt] = build(l, mid);
        R[rt] = build(mid+1, r);
    }
    return rt;
}

inline int update(int pre, int l, int r, int x)
{
    int rt = ++ cnt;
    L[rt] = L[pre]; R[rt] = R[pre]; sum[rt] = sum[pre]+1;
    if (l < r){
        if (x <= mid) L[rt] = update(L[pre], l, mid, x);
        else R[rt] = update(R[pre], mid+1, r, x);
    }
    return rt;
}

inline int query(int u, int v, int l, int r, int k)
{
    if (l >= r) return l;
    int x = sum[L[v]] - sum[L[u]];
    if (x >= k) return query(L[u], L[v], l, mid, k);
    else return query(R[u], R[v], mid+1, r, k-x);
}

int main()
{
    scanf("%d%d", &n, &q);
    for (int i = 1; i <= n; i ++){
        scanf("%d", &a[i]);
        b[i] = a[i];
    }
    sort(b+1, b+1+n);
    m = unique(b+1, b+1+n)-b-1;
    T[0] = build(1, m);
    for (int i = 1; i <= n; i ++){
        int t = lower_bound(b+1, b+1+m, a[i])-b;
        T[i] = update(T[i-1], 1, m, t);
    }
    while (q --){
        int x, y, z;
        scanf("%d%d%d", &x, &y, &z);
        int t = query(T[x-1], T[y], 1, m, z);
        printf("%d\n", b[t]);
    }

 

posted @ 2018-07-27 23:30  planche  阅读(117)  评论(0编辑  收藏  举报