[知识学习] 主席树

Posted xx-queue

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[知识学习] 主席树相关的知识,希望对你有一定的参考价值。

这两天学习了主席树,基本上搞懂了主席树是怎么操作的


主席树,是一种可持久化线段树。最简单的操作就是维护静态区间第 (k)

主席树通过维护历史版本,实现查询区间的有关操作


主席树的原理

假设现在有这么一个序列:(4,1,3,5,2)

问如何求出区间[1,3]内大小为第二的数?

利用大眼观察法,很显然是3

那么让计算机去怎么实现呢?它又没有眼睛

对于这个序列,我们可以先建一颗空的权值线段树,命名为“树0”(方便后面的使用),如图:

技术图片

别告诉我你不知道什么是权值线段树,自己去百度;

现在序列里面第一个数是 (4),我们往树里面插入一个 (4) ,因为要保留历史版本,所以我们对 (4) 这个数新建一颗线段树,命名为“树1”,如图:

技术图片

为什么是这样呢?

(1le 4 le5),故区间[1,5]++;

(4le 4 le5),故区间[4,5]++;

(4le 4 le4),故区间[4,4]++;

其他的还是 (0) ;

懂了没有。。。

继续插入第二个数 (1) ,建成“树2” ,这里不解释了

技术图片

再插入第三个数 (3),建成“树3”

技术图片

OK!现在我们就已经可以求出[1,3]内的大小为第二大的数了

递归操作查询排名应该都会吧?

不会的看这里:

  • 进入[1,5]节点,我们发现他的左儿子的子树个数为(2) , $2le k $ ((k=2)),于是进入[1,3]节点;

  • 然后我们发现[1,3]节点的左儿子子树个数(1 < k) ((k=2)),于是进入[3,3]节点;

  • 此时我们把(k)更新为(1) ((2-1=1));

  • 走到头了,于是就返回3,所以答案就是3,也就是原来的序列区间[1, 3]的第2小就是3

现在你明白了主席树是怎么操作了的吧?


疑问

但是有一个问题:上面我们求的是区间[1,3]的第 (k) 大的数

同理,区间[1,r] ((r in [1,n],r in N))的第 (k) 大数我们也就会求了

那怎么求区间[l,r]的第 (k) 大数呢?

举个例子,求区间[2,3]的第 (k) 大数

我们拿建出来的“树3”减掉“树1”后,再进行如上操作就可以了

这也就是前缀和思想

所以对于区间[l,r] 我们拿“树r”减去“树(l-1)”,再query一下就可以求得答案了


例题与代码

那么主席树就介绍完了,具体实现给个例题让大家看看,还有不懂得可以再参考一下他人的博客

例题:静态区间第 (k) 小(【模板】可持久化线段树1/主席树)

题目链接

(鸣谢@wsk1202提供板子)

#include <bits/stdc++.h>
#define N (200000+5)
#define ls ch[rt][0]
#define rs ch[rt][1]
#define vl ch[vs][0]
#define vr ch[vs][1]//几个宏定义
using namespace std;
int n,m,q;
int rt[N],ch[N<<5][2],tot,val[N<<5];
int a[N],b[N];
inline int query(int x){//离散化
    return lower_bound(b+1,b+m+1,x)-b;
}
inline void update(int &rt,int vs,int l,int r,int k){//新建一棵树
    rt=++tot;
    ls=vl,rs=vr;
    val[rt]=val[vs]+1;
    if(l==r) return;
    int mid=(l+r)>>1;
    if(k<=mid) update(ls,vl,l,mid,k);
    else update(rs,vr,mid+1,r,k);
}
inline int query(int rt,int vs,int l,int r,int k){//询问
    if(l==r) return l;
    int mid=(l+r)>>1;
    int v=val[vl]-val[ls];
    if(k<=v) return query(ls,vl,l,mid,k);
    else return query(rs,vr,mid+1,r,k-v);
}
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+n+1);
    m=unique(b+1,b+n+1)-b-1;
    for(int i=1;i<=n;i++){
        update(rt[i],rt[i-1],1,n,query(a[i]));
    }
    while(q--){
        int l,r,k;
        scanf("%d%d%d",&l,&r,&k);
        printf("%d
",b[query(rt[l-1],rt[r],1,n,k)]);
    }
    return 0;
}

以上是关于[知识学习] 主席树的主要内容,如果未能解决你的问题,请参考以下文章

[数据结构] 主席树初识(理论,代码待补)

主席树学习--入门

动态主席树 优化版

主席树学习笔记

主席树初学 SPOJ3267

主席树的学习