「可持久化线段树」学习笔记

  • 前言

    未经允许,请勿转载。

    前置知识:线段树。

    主席树是一个东西的数据结构。

    竟然一遍AC。太难得了。

    主要参考这个这个这个


  • 求什么

    比如洛谷的P3834 【模板】可持久化线段树 1(主席树)

    给定 \(n\) 个整数构成的序列,将对于指定的闭区间查询其区间内的第 \(k\) 小值。

    其中,对于 \(100\%\) 的数据满足:\(1 \leq n,m \leq 2\times 10^5 , -10^9 \le a_i \le 10^9\)


  • 怎么求 & 主席树的思想

    比如一个序列 \(25957,6405,15770,26287,26465\)

    因为只是要求第 \(k\) 小值,所以我们可以考虑先把原序列 \(a_i\) 离散化

    所以离散后成了这样 \(3,1,2,4,5\)

    然后我们要先建一个空的线段树。

    然后对于每个区间 \([1,i]\) 建一个线段树

    对于每个线段树,存上 \([1,i]\) 中所有值离散后的数。

    如图,为各个区间的线段树:

    那么接下来解决的就是查询,求区间 \([l,r]\) 中第 \(k\) 小的值。

    比如,要查询的是 \([2,4]\) 中第 \(2\) 大的数。

    我们选择 \([1,1]\)\([1,4]\) 这两个线段树:

    然后把他们对应点相减:

    我们设每个点(区间)的值为\(sum[l,r]\),然后递归查询。

    第一个节点的两个儿子 \([1,2]\)\([3,5]\) ,要查找的是第 \(k=2\) 个,因为 \(sum[1,2] \ge k\) ,所以选择 \([1,2]\)

    \([1,1]\)\([2,2]\) 中选择,因为 \(sum[1,1] < k\) ,所以选择 \([2,2]\) 并且把查找第 \(k\)改为查找 \(k-sum[1,1]\)

    因为此时 \(l=r\) ,查找结束,结果为 \(2\)

    然后就解决了。

    然后发现空间时间炸了。

    在上述过程中,其实可以发现,相邻的线段树,即 \([1,i]\)\([1,i+1]\) 这两个区间只有一条链不同。

    所以我们可以直接把树建成这样:

    这样时间和空间的问题也解决了。

    那么也就解决了这个问题。


  • 代码

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    using namespace std;
    const int Maxn=2e5+5,Maxm=(Maxn<<5)+5;
    int n,m,p,cntn,a[Maxn],b[Maxn];
    int lc[Maxm],rc[Maxm],rt[Maxm],sum[Maxm];
    void build(int &o,int l,int r)//建个空线段树 
    {	
        o=++cntn;
        if(l==r)return;
        int mid=(l+r)>>1;
        build(lc[o],l,mid);
        build(rc[o],mid+1,r);
    }
    int modify(int o,int l,int r)
    {	
        int oo=++cntn;
        lc[oo]=lc[o];rc[oo]=rc[o];
        sum[oo]=sum[o]+1;
        if(l==r)return oo;
        int mid=(l+r)>>1;
        if(p<=mid)lc[oo]=modify(lc[oo],l,mid);
        else rc[oo]=modify(rc[oo],mid+1,r);
        return oo;
    }
    int query(int u,int v,int l,int r,int k) //查询 
    {	
        int mid=(l+r)>>1,x=sum[lc[v]]-sum[lc[u]];
        if(l==r)return l;
        if(x>=k)return query(lc[u],lc[v],l,mid,k);
        else return query(rc[u],rc[v],mid+1,r,k-x);
    }
    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+1+n); //离散 
        int q=unique(b+1,b+1+n)-b-1; //去重 
        build(rt[0],1,q);
        for(int i=1;i<=n;i++)
        {	
            p=lower_bound(b+1,b+1+q,a[i])-b;
            rt[i]=modify(rt[i-1],1,q);
        }
        while(m--)
        {	
            int x,y,z;
            scanf("%d%d%d",&x,&y,&z);
            printf("%d\n",b[query(rt[x-1],rt[y],1,q,z)]);
        }
        return 0;
    }
    

\[\text{by Rainy7} \]

posted @ 2020-02-28 18:49  Rainy7  阅读(275)  评论(0编辑  收藏  举报