主席树详解

来源:互联网 发布:广州女人街网络批发 编辑:程序博客网 时间:2024/05/29 07:42

主席树是一种线段树的变形,可以解决区间第k大的问题。下面我谈谈自己的理解,尽可能的讲清楚些。
假设现在有序列 a[5] = {1 , 4 , 5 , 3 , 2},有q个询问{l , r , k}问你[l , r]区间第k小的值是多少?

  1. 容易想到对被询问区间排个序,然后输出第k个数。这种方法最直观,但也是最费时的(时间复杂度爆炸),不可取。
  2. 另一种是先建立一个空树,再将[l , r]区间的值插入到树中,然后查找。

空树:

空树
现在将[2 , 4]区间的值(4 , 5 , 3)插入到这颗树里.

插入后:

插入后
可以看到插入以后,[3,3],[4, 4] ,[5 5]的值变成了1代表[2 , 4]区间的数分布情况。有了这个树我们只需要遍历一遍就可以解决该区间第k大的问题了。但是这种方法有两个致命的缺陷!

  1. 每次查询都要重新建树,时间复杂度很高。
  2. 对每一次查询都要新建立一个树,太吃内存了,空间复杂度爆炸。
    到了这里,只要解决了这两个问题,这个算法就是一个高效的算法了!我们注意到在插入的时候,每插入一个数,影响的只是这棵树上的一条路径上的
    log2n
    个点受影响,利用这个性质,建树的时候可以只在前一个线段树的基础上增添
    log2n
    个点就可以了,这样复杂度大大降低了。
    这里写图片描述
    可以看到将3插入树中的时候,只影响了3个点。

我们可以建立n课树,对于树tree[i]插入的是[1,i]。这样在求区间[l,r]时,tree[r] - tree[l-1]就是我们要找的树,遍历这颗树就行了。自此主席树求静态区间第k大讲完,等再有空了补上带修改的区间第k大。

我的模板:

#include <bits/stdc++.h>using namespace std;const int maxn = 1e5+10;struct A{    int x , idx;    bool operator < (const A &rhs) const    {        return x < rhs.x;    }};A a[maxn];struct tree{    int L , R , sum;}T[maxn*40];int n , m , cnt , ran[maxn] , root[maxn];void insert(int num , int &x , int l , int r){    T[cnt++] = T[x] , x = cnt - 1;    T[x].sum += 1;    if(l == r) return ;    int mid = (l + r) / 2;    if(num <= mid)        insert(num , T[x].L , l , mid);    else        insert(num , T[x].R , mid+1 , r);}int query(int i , int j , int k , int l , int r){    if(l == r) return l;    int t = T[T[j].L].sum - T[T[i].L].sum;    int mid = (l + r) / 2;    if(t >= k)        return query(T[i].L , T[j].L , k , l , mid);    return query(T[i].R , T[j].R , k-t , mid+1 , r);}int main(){    int t , i , j  , k;    T[0].L = T[0].R = T[0].sum = 0;    root[0] = 0;    scanf("%d", &t);    while(t--)    {        scanf("%d %d", &n , &m);        for(i=1; i<=n; i++)        {            scanf("%d", &a[i].x);            a[i].idx = i;        }        sort(a+1 , a+n+1);        for(i=1; i<=n; i++)            ran[a[i].idx] = i; //主席树必须要离散化。        cnt = 1;        for(i=1; i<=n; i++)        {            root[i] = root[i-1];            insert(ran[i] , root[i] , 1 , n); //建立n颗线段树        }        while(m--)        {            scanf("%d %d %d", &i , &j , &k);            printf("%d\n", a[query(root[i-1] , root[j] , k , 1 , n)].x);        }    }    return 0;}
原创粉丝点击