「可持久化线段树」学习笔记
-
求什么
比如洛谷的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; }