码迷,mamicode.com
首页 > 其他好文 > 详细

主席树模板

时间:2018-04-03 21:58:31      阅读:151      评论:0      收藏:0      [点我收藏+]

标签:i++   update   查询   函数式   应该   数字   主席树   build   需要   

   主席树,又名函数式线段树.是fotile主席创建出来的这个数据结构,所以叫主席树.

   然后这里有一些最常用的主席树需要解决的问题.

 主席树_ 求区间K大值

   题目大意:

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

  

   首先因为是多次离线求区间的K大值,而且又因为数据范围的限制,所以传统的线段树显然是不可行的.然后这里我们就需要用到主席树.

   主席树,首先有几个东西需要明确.

   1. 主席树内有多棵线段树, 而且利用到了类似于前缀和的思想和做法.

   2. 主席树所用的储存数据的结构,是比较巧妙的,即就把前后一致的一些一样结点多棵线段树共用.

   

   由于数据原因,所以我们一般需要先将数据离散化一波.

   然后就开始讲每一棵线段树的建立.

 

  

   这里引用一组数据 :

  7 1
  1 5 2 6 3 7 4
  2 5 3

  首先 我们需要建立一棵空树.

  技术分享图片

   

    然后依次将所有的结点 按照离散化所得到的顺序将结点一个一个插进去.

    在插入的时候,更新sum数组. sum数组所起的所用就是一个前缀和的作用,表示这个结点包含了几个结点.

    这一步操作,类似于Splay.

    

    技术分享图片

  技术分享图片

  技术分享图片

        

      直到把所有数字插入以后,情况是这样的。

  技术分享图片

      

    那么建树的具体过程大致如上。接下来我们考虑查询。

    要查询[2, 5]中第3大的数我们首先把第1棵线段树和第5棵拿出来。

 

            技术分享图片

            技术分享图片

     然后查询就是前面说的前缀和的做法了.

     第1棵线段树和第5棵线段树,之间的差值部分就是我们所需要查询的线段树部分.

     查询的时候有类似于Splay 的做法. 即找到当前结点,看是否之前左儿子统计的前缀和是否大于k,如果大于k, 那么我们就继续递归下去.

     直到找到一个只有一个元素的结点,即为我们所需要的答案.

 

   然后 代码(主席树的代码比较短,需要花时间去消化):

      

 1 #include<bits/stdc+.h>
 2 const int maxn=200005;
 3 int sz,T,n,m,rt[maxn],tot,lc[maxn*20],rc[maxn*20],sum[maxn*20],a[maxn],b[maxn];
 4 using namespace std;
 5 void update(int &o,int l,int r,int last,int v){
 6     //o是当前结点 last是上一次更改过的结点.
 7     //将每一个结点插到该插好的地方.
 8     o=++tot;
 9     lc[o]=lc[last];
10     rc[o]=rc[last];
11     //此处借用上一次的结点
12     sum[o]=sum[last]+1;
13     //因为多插入了一个结点 所以前缀和数组需要加上一个1
14     //同时sum 数组起到一个前缀和的作用
15     if(l==r) return;
16     int mid=(l+r)>>1;
17     if(v<=mid) update(lc[o],l,mid,lc[last],v);
18     else update(rc[o],mid+1,r,rc[last],v);
19     //把每一个数安装到它应该在的地方.
20 }
21 int query(int ql,int qr,int l,int r,int k){
22     if(l==r) return l;    
23     int mid=(l+r)>>1;
24     //和splay 类似的查询 
25     int cnt=sum[lc[qr]]-sum[lc[ql]];
26     if(cnt>=k) return query(lc[ql],lc[qr],l,mid,k);
27     else return query(rc[ql],rc[qr],mid+1,r,k-cnt);
28 }
29 int main()
30 {
31      scanf("%d%d",&n,&m);
32      for(int i=1;i<=n;i++) { scanf("%d",&a[i]);b[i]=a[i];}
33      sort(b+1,b+n+1);
34      sz=unique(b+1,b+n+1)-(b+1);
35      tot=0;
36      rt[0]=++tot;
37      //每一棵子树的根结点
38      //build(rt[0],1,sz);
39      for(int i=1;i<=n;i++) a[i]=lower_bound(b+1,b+sz+1,a[i])-b;  //离散化 
40      for(int i=1;i<=n;i++) update(rt[i],1,sz,rt[i-1],a[i]);      
41      //建造每一棵线段树   
42      for(int i=1;i<=m;i++){
43        int l,r,k;scanf("%d%d%d",&l,&r,&k);
44        printf("%d\n",b[query(rt[l-1],rt[r],1,sz,k)]);
45      } 
46    return 0;
47 }

 

      

注: 以上有一些图片转自另一博客       

     

技术分享图片

主席树模板

标签:i++   update   查询   函数式   应该   数字   主席树   build   需要   

原文地址:https://www.cnblogs.com/Kv-Stalin/p/8710911.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!