zoukankan      html  css  js  c++  java
  • 入坑 可持久化线段树——主席树

    主席树主要用来处理历史版本查询。
    这一篇我只想想先说说对于区间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);
        }
    }

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

  • 相关阅读:
    Appium 服务关键字(转)
    android自动化之appium的环境搭建
    关于性能测试几个名词概念的说明
    关于.net服务启动注册到zookeeper,但是注册节点20分钟自动消失解决办法
    关于tomcat启动报“this web application instance has been stopped already”的处理
    loadrunner在win10破解提示:Cannot save the license information because acceses to the registry is denied的解决办法
    Teamcity部署.net服务“无法连接到远程服务器”解决方式
    数据库主从不同步问题随笔
    eclipse 常用快捷键
    在linux中安装jdk以及tomcat并shell脚本关闭启动的进程
  • 原文地址:https://www.cnblogs.com/QTY2001/p/7632651.html
Copyright © 2011-2022 走看看