-
求什么
比如洛谷的P3834 【模板】可持久化线段树 1(主席树)。
给定 (n) 个整数构成的序列,将对于指定的闭区间查询其区间内的第 (k) 小值。
其中,对于 (100\%) 的数据满足:(1 leq n,m leq 2 imes 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 ",b[query(rt[x-1],rt[y],1,q,z)]); } return 0; }