入坑 可持久化线段树——主席树

主席树主要用来处理历史版本查询。
这一篇我只想想先说说对于区间Kth的处理。
如果把区间的每一位都视为一次更新(可以视为数据在0~inf范围的一次更新)求区间Kth就转变为某个元素在某段历史中出现的Kth那么一个值是多少。
每次修改,有别于普通线段数,完全新造一棵树(时空间都不允许)。
这也就是主席树有别于于普通线段数的地方:每次修改都是在原来的基础上,加了一条链。
类似这样
对于没更新的子节点,连接到原来的节点,而新插入的值造个新节点就好了。
对于网上除了LadyLex的鲜有指针版的板子,那我写个指针版(现学现卖。。)好了。
递归版

inline void insert(tree* pre,tree* &x,int l,int r)
    {
        x->ch[0]=pre->ch[0];
        x->ch[1]=pre->ch[1];
        x->sum=pre->sum+1;
        if(l==r)return;
        int mid=(l+r)>>1;
        if(pos<=mid)insert(pre->ch[0],x->ch[0],l,mid);
        else insert(pre->ch[1],x->ch[1],mid+1,r);
    }

也可以写个二分(略)
然后,把主席树建成权值线段树,维护区间元素的总个数,就可以用来求Kth了。
想一想为什么。
因为已经把区间每个位置当做一个新的版本了,新建了n条链。可以看出它具有区间可减行。很容易确定我们要找的区间左右端点所指向的root,维护了权值区间元素的个数,也就很容易找到这个区间的kth。(先判断左子区间元素个数。。以此类推)
这次我写了个二分版


    inline int q(tree* x1,tree* x2,int k,int l,int r)
    {
        while(l<r)
        {
            int cmp=(x2->ch[0]->sum-x1->ch[0]->sum),mid=(l+r)>>1;
            if(cmp>=k)x2=x2->ch[0],x1=x1->ch[0],r=mid;
            else x2=x2->ch[1],x1=x1->ch[1],l=mid+1,k-=cmp;
        }
        return b[r];
    }

完整的板子(poj2104)
这个板子对数据进行了离散,其实因为是动态开点,不离散也是没问题的。(20w的区间长,-inf~inf都没问题)

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 100005
using namespace std;
int n,m,pos,a[N],b[N];
namespace chairtree
{
    struct tree
    {
        tree* ch[2];int sum;
        tree(){sum=0;ch[0]=ch[1]=NULL;}
    }*root[N],*null=new tree();
    inline tree* newtree()
    {
        tree *o=new tree();
        o->ch[0]=o->ch[1]=null;
        return o;
    }
    inline void insert(tree* pre,tree* &x,int l,int r)
    {
        x->ch[0]=pre->ch[0];
        x->ch[1]=pre->ch[1];
        x->sum=pre->sum+1;
        if(l==r)return;
        int mid=(l+r)>>1;
        if(pos<=mid)insert(pre->ch[0],x->ch[0],l,mid);
        else insert(pre->ch[1],x->ch[1],mid+1,r);
    }
    inline int q(tree* x1,tree* x2,int k,int l,int r)
    {
        while(l<r)
        {
            int cmp=(x2->ch[0]->sum-x1->ch[0]->sum),mid=(l+r)>>1;
            if(cmp>=k)x2=x2->ch[0],x1=x1->ch[0],r=mid;
            else x2=x2->ch[1],x1=x1->ch[1],l=mid+1,k-=cmp;
        }
        return b[r];
    }
}
using namespace chairtree;
int main()
{
    scanf("%d%d",&n,&m);
    null->ch[0]=null;null->ch[1]=null;
    for(int i=1;i<=n;i++)scanf("%d",&a[i]),b[i]=a[i],root[i]=newtree();
    sort(b+1,b+n+1);
    root[0]=newtree();
    for(int i=1;i<=n;i++)
    {
        pos=lower_bound(b+1,b+n+1,a[i])-b;
        insert(root[i-1],root[i],1,n);
    }
    int l,r,k;
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&l,&r,&k);
        printf("%d
",q(root[l-1],root[r],k,1,n));
    }
}

但是这样的主席树只能维护静态的kth查询。
那么对于动态呢?若以同样方式建树,依次枚举之后的链进行修改,很明显不行。
考虑用树状数组。主席树套树状数组。。简直了
通过树状数组的方式->统计当前链要从之前那几条链转移过来,之后见边时同步更新这些链即可。
查询,修改同理。
直接上个板子吧(bzoj1901权限题)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <vector>
#define N 10005
#define inf 1000000000
using namespace std;
int n,m,a[N];
namespace chairtree
{
    struct tree
    {
        tree* lc;tree* rc;
        int sum;
        tree(){lc=rc=NULL;sum=0;}
        inline void updata(){sum=lc->sum+rc->sum;}
    }*root[N],*null=new tree();
    vector<tree*> v[4];
    tree* newtree()
    {
        tree* o=new tree();
        o->lc=o->rc=null;
        return o;
    }
    inline int low(int x){return x&(-x);}
    void get_ins(int id,int x)
    {
        v[id].clear();
        while(x<=n){v[id].push_back(root[x]);x+=low(x);}
    }
    void get_q(int id,int x)
    {
        v[id].clear();
        while(x>0){v[id].push_back(root[x]);x-=low(x);}
    }
    inline void insert(vector<tree*> o,int l,int r,int k,int h)
    {
        int len=o.size(),mid;
        while(l<r)
        {
            mid=(l+r)>>1;
            for(int i=0;i<len;i++)if(o[i]!=null)o[i]->sum+=h;
            if(k<=mid)
            {
                for(int i=0;i<len;o[i]=o[i]->lc,i++)
                    if(o[i]->lc==null)o[i]->lc=newtree();
                r=mid;
            }
            else
            {
                for(int i=0;i<len;o[i]=o[i]->rc,i++)
                    if(o[i]->rc==null)o[i]->rc=newtree();
                l=mid+1;
            }
        }
        for(int i=0;i<len;i++)
        {
            if(o[i]==null)o[i]=newtree();
            o[i]->sum+=h;
        }
    }
    inline int q(int a,int b,int l,int r,int k)
    {
        get_q(1,a-1);get_q(2,b);
        int mid,len1=v[1].size(),len2=v[2].size();
        while(l<r)
        {
            mid=(l+r)>>1;int t=0;
            for(int i=0;i<len1;i++)if(v[1][i]!=null)t-=v[1][i]->lc->sum;
            for(int i=0;i<len2;i++)if(v[2][i]!=null)t+=v[2][i]->lc->sum;
            if(t>=k)
            {
                for(int i=0;i<len1;i++)v[1][i]=v[1][i]->lc;
                for(int i=0;i<len2;i++)v[2][i]=v[2][i]->lc;
                r=mid;
            }
            else
            {
                for(int i=0;i<len1;i++)v[1][i]=v[1][i]->rc;
                for(int i=0;i<len2;i++)v[2][i]=v[2][i]->rc;
                l=mid+1;k-=t;
            }
        }
        return r;
    }
    inline void change(int l,int k)
    {
        get_ins(1,l);
        insert(v[1],0,inf,a[l],-1);
        insert(v[1],0,inf,k,1);
        a[l]=k;
    }
}
using namespace chairtree;
int main()
{
    scanf("%d%d",&n,&m);null->lc=null->rc=null;
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)root[i]=newtree();
    for(int i=1;i<=n;i++)get_ins(1,i),insert(v[1],0,inf,a[i],1);
    int l,r,k;char s[2];
    while(m--)
    {
        scanf("%s",s);
        if(s[0]=='Q')scanf("%d%d%d",&l,&r,&k),printf("%d
",q(l,r,0,inf,k));
        else scanf("%d%d",&l,&k),change(l,k);
    }
}

那么同类的问题:历史版本查询,只要把权值线段树改成普通线段树,把每一个位置作为一个“版本”改成真正的版本即可。

原文地址:https://www.cnblogs.com/QTY2001/p/7632651.html