可持久化线段树(主席树)
题外话:可能很多人的知道主席树,但就没人好奇为什么叫主席树吗?发明者缩写HJT,所以就叫主席树了。
主席树
解决区间第k大,以及需要查找历史数据的区间问题。
例题
以下为区间第k大问题的主席树解法。
先离散化。
对于求[1,n]区间的问题,只需要建一棵值域平衡树,维护在原数组中有多少个数在当前值域,查询时就看lson与k的大小关系然后递归求解。
对于求[x,y]区间的问题,考虑前缀和思想,建n棵值域平衡树,分别维护原数组[1,i]区间的情况,查询时就看第y棵的lson-第x-1棵的lson与k的大小关系然后递归求解。
但是这样做的空间复杂度是n^2的,多半要炸。
所以要用主席树来优化空间。
对应[1,i]和[1,i+1]区间的两棵树只有i+1这1个数的区别,受i+1影响的只有logn个点而其余的点没有改变,所以直接把第i+1号树的一些边直接连到i号树上,这样空间复杂度就只有nlogn了。
#include<bits/stdc++.h>
using namespace std; const int MM=100010,LOG=20; int x,y,z,t[MM],L[MM*LOG],R[MM*LOG],sum[MM*LOG],a[MM],b[MM],n,m,q,tot; int build(int l,int r) { int rt=++tot; if(l<r) { L[rt]=build(1,(l+r)/2); R[rt]=build((l+r)/2+1,r); } return rt; } int update(int pre,int l,int r,int x) { int rt=++tot; L[rt]=L[pre],R[rt]=R[pre],sum[rt]=sum[pre]+1; if(r>l) { if(x<=(l+r)/2) L[rt]=update(L[pre],l,(l+r)/2,x); else R[rt]=update(R[pre],(l+r)/2+1,r,x); } return rt; } int query(int rt1,int rt2,int l,int r,int k) { if(l==r)return l; int x=sum[L[rt2]]-sum[L[rt1]]; if(x>=k)return query(L[rt1],L[rt2],l,(l+r)/2,k); else query(R[rt1],R[rt2],(l+r)/2+1,r,k-x); } int main() { cin>>n>>q; for(int i=1;i<=n;i++) cin>>a[i],b[i]=a[i]; sort(b+1,b+n+1); m=unique(b+1,b+n+1)-b-1; t[0]=build(1,m); for(int i=1;i<=n;i++) { a[i]=lower_bound(b+1,b+m+1,a[i])-b; t[i]=update(t[i-1],1,m,a[i]); } while(q--) { cin>>x>>y>>z; cout<<b[query(t[x-1],t[y],1,m,z)]<<endl; } return 0; }