关于树论主席树

Posted

tags:

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

很后悔之前在XGC大佬讲的时候没认真听(其实讲的不q不c,幸好了解了一下),现在搞搞差不多理解了。

这个东西是线段树的进化版,强大在于实现了可持久化,后一刻可以参考前一刻的状态。
裸题:给n(1<=n<=100000)个数字a[1],a[2],......,a[n](0<=a[i]<=1000000000),m(1<=m<=100000)次询问l到r之间的第k小的值。
对于这种频繁询问l~r的第k小,线段树原地爆炸。

所以说要用主席树。
首先对于线段树的定义有一点不同(线段树求他的最小值应该是按数列位置排,管理的点有个mn记录区间最小),而在主席树,他的叶子节点是记录这个区间有多少个这个数,举个例子,比如数列里有3个1,那管理1~1的点c值为3,然后回溯更新父亲,所以说,为了防止读入的数值太大以至于爆内存,所以说要离散化。
对于这个序列每一个前缀,都为他建一棵新树,这个数列管理1~i的值,然而不用想就知道,建这么多树不爆才怪,那怎么办?可以发现,管理1~i-1和管理1~i的树只差一个点,而a[i]只会影响到从叶子节点到根一条路径的值,那这两棵树就可以共用不影响的点,达到省空间的目的。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
struct node
{
    int lc,rc,c;
}tr[2100000];int cnt;
int a[110000],b[110000],rt[110000];
int maketree(int x,int l,int r,int p)
{
    if(x==0)x=++cnt;tr[x].c++;//开新点,然后管理的人数++ 
    if(l==r)return x;
    int mid=(l+r)/2;
    if(p<=mid)tr[x].lc=maketree(tr[x].lc,l,mid,p);//a[i]应该隶属那个子树 
    else       tr[x].rc=maketree(tr[x].rc,mid+1,r,p);
    return x;
}
int Merge(int x,int y)
{
    if(x==0||y==0)return x+y;//假如说x没有这个节点,而y有,那就不用开新点,直接连过去,也就是说一个的点有可能多个父亲
    tr[x].c+=tr[y].c;//x的总人数加上y的 
    tr[x].lc=Merge(tr[x].lc,tr[y].lc);
    tr[x].rc=Merge(tr[x].rc,tr[y].rc);
    return x;
}
int findans(int x,int y,int l,int r,int k)
{
    if(l==r)return b[l];//l是离散了的值,找回原来的 
    int c=tr[tr[x].lc].c-tr[tr[y].lc].c;//得出区间真正有的点数,就是前缀和的应用 
    int mid=(l+r)/2;
    if(k<=c)return findans(tr[x].lc,tr[y].lc,l,mid,k);
    else     return findans(tr[x].rc,tr[y].rc,mid+1,r,k-c);
}
int n,m;
int erfen(int k)//二分实际是得出离散值,不然线段树空间爆炸 
{
    int l=1,r=n,mid,ans;
    while(l<=r)
    {
        mid=(l+r)/2;
        if(b[mid]<=k)
        {
            l=mid+1;
            ans=mid;
        }
        else r=mid-1;
    }
    return ans;
}
char ss[10];
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        b[i]=a[i];
    }
    sort(b+1,b+n+1);
    cnt=0;memset(rt,0,sizeof(rt));
    for(int i=1;i<=n;i++)
    {
        rt[i]=maketree(rt[i],1,n,erfen(a[i]));//这棵树里就记录了a[i] 
        rt[i]=Merge(rt[i],rt[i-1]);//上一棵树记录a[1]~a[i-1],让新的和这个合并一下(上一棵树保留),就得到a[1]~a[i]了 
    }
    int x,y,k;
    while(m--)
    {
        scanf("%d%d%d",&x,&y,&k);
        printf("%d\n",findans(rt[y],rt[x-1],1,n,k));//类似求前缀和,主席树就是按前缀建树啊! 
    }
    return 0;
}

 

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

关于树论动态树问题(LCT)

关于树论动态点分治

关于树论左偏树

关于主席树

树论讲解——树基础

关于树状数组套主席树的一些博客