主席树 学习笔记

对于询问$[1,n]$的第$k$小数,我们都知道直接上权值线段树就行了。那么对于任意区间的第$k$小数呢?

暴力一点,每次开一颗线段树。空间肯定爆炸。那么此时,主席树便应运而生。

主席树的主要思想就是:保留每次插入操作时的历史版本,以便查询区间第$k$小的数。先说流程。

1.先建一颗空的权值线段树,$[1,len]$。

2.从$1$到$n$对于每个结点都新建一颗权值线段树,$i$结点的线段树根据$i-1$的更新,即在原基础上进行加的操作。

3.若查询$[l,r]$则拿出第$l-1$颗和第$r$颗线段树进行比较,他们之间的差值就是$[l,r]$区间的元素个数。查找第$k$小,就看左儿子的大小$x$。如果$k\leq x$,那么答案肯定在左儿子,反之则在右儿子。注意此时$k$要更新成$k-x$,因为在右儿子的区间里相对大小会发生变化。

主席树有着满足前缀和和树上差分等优秀性质(感性理解),所以不管是树还是序列都可以维护。

注意:使用主席树时请不要吝啬你的空间,不然会出现奇奇怪怪的错误。一般来说我都开$n\log n$,实际上开$8*10^6$都可以。

更多内容详见OI Wiki。

代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=5000005;
int n,m,len,a[maxn],b[maxn],sum[maxn],ls[maxn],rs[maxn],tot,rt[maxn];
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline int getpos(int x){return lower_bound(b+1,b+len+1,x)-b;}
inline int build(int l,int r)
{
    int root=++tot;
    if (l==r) return root;
    int mid=(l+r)>>1;
    ls[root]=build(l,mid);
    rs[root]=build(mid+1,r);
    return root;
}
inline int update(int k,int l,int r,int root)
{
    int dir=++tot;
    ls[dir]=ls[root],rs[dir]=rs[root];sum[dir]=sum[root]+1;
    int mid=(l+r)>>1;
    if (l<r)
    {
        if (k<=mid) ls[dir]=update(k,l,mid,ls[root]);
        else rs[dir]=update(k,mid+1,r,rs[root]);
    }
    return dir;
}
inline int query(int u,int v,int l,int r,int k)
{
    if (l==r) return l;
    int x=sum[ls[v]]-sum[ls[u]],mid=(l+r)>>1;
    if (k<=x) return query(ls[u],ls[v],l,mid,k);
    else return query(rs[u],rs[v],mid+1,r,k-x);
}
signed main()
{
    n=read(),m=read();
    for (int i=1;i<=n;i++) a[i]=read(),b[i]=a[i];
    sort(b+1,b+n+1);
    len=unique(b+1,b+n+1)-b-1;
    rt[0]=build(1,len);
    for (int i=1;i<=n;i++) rt[i]=update(getpos(a[i]),1,len,rt[i-1]);
    while(m--)
    {
        int l=read(),r=read(),k=read();
        printf("%lld\n",b[query(rt[l-1],rt[r],1,len,k)]);
    }
    return 0;
}

 

posted @ 2020-07-17 19:44  我亦如此向往  阅读(149)  评论(0编辑  收藏  举报