• 可持久化线段树(主席树)(图文并茂详解)【poj2104】【区间第k大】


    这里写图片描述
    [pixiv] https://www.pixiv.net/member_illust.php?mode=medium&illust_id=63740442
    向大(hei)佬(e)实力学(di)习(tou)

    主席树主要用于处理区间的问题,其中区间不一定是单纯的区间,还是可以将题目中的一些信息转化一下(如 操作、时间等),即需要查询区间信息的数据,还需要用线段树用的题目,就可以用主席树。
    以前有畏难心理,总觉得主席树好复杂,各种逃避。后来再次学习,总算是学会了基础的东西。

    主席树通常是前缀和和线段树的结合,下面以一道经典题:区间第k大 为例

    第k大,想到用值域线段树。区间可以转化为前缀,相减即可。即对每一个前缀节点建线段树,但如果暴力建,就完蛋了(^10),完美爆空间

    仔细一想,每一个线段树和前一个相比,其实只修改了一个点。我们只需要新建树根到新增叶子节点路径上的点就可以了,其他的指向前一棵树的对应位置
    放一张图来理解的更直观一些(灰色格子是虚点)
    这里写图片描述
    由此可见,空间复杂度为n*logn,可以接受
    建树代码

    void insert(Node *&ndn,Node *ndp,int le,int ri,int pos){//ndn当前的节点,注意要加&修改.ndp前一个节点,同步
        ndn=newnode();
        ndn->sum=ndp->sum+1;
        if(le==ri) return ;
        ndn->ls=ndp->ls,ndn->rs=ndp->rs;
        int mid=(le+ri)>>1;
        if(pos<=mid) insert(ndn->ls,ndp->ls,le,mid,pos);
        else insert(ndn->rs,ndp->rs,mid+1,ri,pos);
    }

    这里有一个小技巧。因为指针常常指出去,就RE了,为了避免这一点以及不需要对root[0]先花2*n的空间建空树,我们这里用一个null的自环来表示空节点。妈妈再也不用担心我的指针写挂啦~(≧▽≦)/~

    struct Node{
        Node *ls,*rs;
        int sum;
    }*root[N],*null,pool[N*50],*tail=pool;
    int main(){
        null=++tail;
        null->ls=null->rs=null;
        null->sum=0;
        root[0]=null;
    }

    然后就是查询,其实很简单,就像普通的值域线段树和前缀和区间查询就可以了

    int query(Node *ndn,Node *ndp,int le,int ri,int pos){
        if(le==ri) return le;
        int lsum=ndn->ls->sum - ndp->ls->sum;//前缀和
        int mid=(le+ri)>>1;
        if(pos<=lsum) return query(ndn->ls,ndp->ls,le,mid,pos);
        else return query(ndn->rs,ndp->rs,mid+1,ri,pos-lsum);
    }

    关键就解决了,至于区间第k大的题需要离散化,就不赘述了
    完整代码

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define ll long long 
    using namespace std;
    
    const int N=100000+5;
    const int oo=1000000000+7;
    
    struct Node{
        Node *ls,*rs;
        int sum;
    }*root[N],*null,pool[N*50],*tail=pool;
    struct aa{
        int nu,val;
    }hh[N];
    int n,m,rank[N];
    
    bool cmp1(aa a,aa b){
        return a.val<b.val;
    }
    bool cmp2(aa a,aa b){
        return a.nu<b.nu;
    }
    Node *newnode(){
        Node *rt=++tail;
        rt->ls=rt->rs=null;
        rt->sum=0;
        return rt;
    }
    void insert(Node *&ndn,Node *ndp,int le,int ri,int pos){
        ndn=newnode();
        ndn->sum=ndp->sum+1;
        if(le==ri) return ;
        ndn->ls=ndp->ls,ndn->rs=ndp->rs;
        int mid=(le+ri)>>1;
        if(pos<=mid) insert(ndn->ls,ndp->ls,le,mid,pos);
        else insert(ndn->rs,ndp->rs,mid+1,ri,pos);
    }
    int query(Node *ndn,Node *ndp,int le,int ri,int pos){
        if(le==ri) return le;
        int lsum=ndn->ls->sum - ndp->ls->sum;
        int mid=(le+ri)>>1;
        if(pos<=lsum) return query(ndn->ls,ndp->ls,le,mid,pos);
        else return query(ndn->rs,ndp->rs,mid+1,ri,pos-lsum);
    }
    int main(){
        null=++tail;
        null->ls=null->rs=null;
        null->sum=0;
        root[0]=null;
    
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++){
            scanf("%d",&hh[i].val);
            hh[i].nu=i;
        }
        sort(hh+1,hh+n+1,cmp1);
        int sz=0,tmp=0;
        for(int i=1;i<=n;i++){
            if(tmp!=hh[i].val){
                sz++;tmp=hh[i].val;
                rank[sz]=tmp;hh[i].val=sz;
            }
            else hh[i].val=sz;
        }
        sort(hh+1,hh+n+1,cmp2);
        for(int i=1;i<=n;i++)
            insert(root[i],root[i-1],1,sz,hh[i].val);
        int x,y,k;
        while(m--){
            scanf("%d%d%d",&x,&y,&k);
            printf("%d
    ",rank[query(root[y],root[x-1],1,sz,k)]);
        }
        return 0;
    }

    当然,主席树不只可以解决区间第k大的问题,灵活的变形后还可以解决不少问题。前段时间考了一套大佬的自编题,第一题就是变型的主席树,附上链接:
    http://blog.csdn.net/coco56181712/article/details/75647313

  • 相关阅读:
    C加加学习之路 1——开始
    哈夫曼树C++实现详解
    Linux常用命令
    Accp第二章:基础知识
    第一章Accp 8.0
    泛型集合
    深入C#数据类型
    初始wondows系统
    深入.NET框架
    二至十五章总结
  • 原文地址:https://www.cnblogs.com/LinnBlanc/p/7763154.html
Copyright © 2020-2023  润新知