参考文献链接: https://blog.youkuaiyun.com/a_forever_dream/article/details/80450549
http://www.cnblogs.com/Empress/p/4652449.html
主席树是一种可持久化动态线段树,常用于求区间k小值问题。
其本质为建n棵前缀和线段树,也就是n个版本,第i个版本囊括区间[1~i],这里的i指的是第i个数。那么求第l到第r个数的区间k小时,便可以转化为求query(r)-query(l-1)。
对于一个序列,进行排序和unique去重,得到一个长度为s的元素互不相同的数组,通过其下标离散化减小空间消耗。易得每颗线段树大小都为s,重复数字只需数量加一即可。
unique函数详解:https://www.cnblogs.com/wangkundentisy/p/9033782.html
但这并没有结束,若是对于n<=200000,每个i开一颗线段树,空间复杂度得上天。可以发现,第i棵线段树与第i-1棵并没有太多节点信息改变,实际上最多有logn个节点改变。所以,第i棵线段树可以与第i-1棵线段树共用信息相同的节点,可以省下大量浪费的空间。
附上洛谷P3834 【模板】可持久化线段树 1(主席树)代码:
#include<bits/stdc++.h>
using namespace std;
int tree[200001*40];
int ls[200001*40];
int rs[200001*40];
int rt[200001];
int a[200001];
int b[200001];
int n,m,cnt;
int build(int l,int r){
int id=++cnt;
int mid=(l+r)>>1;
if(l==r) return id;
ls[id]=build(l,mid);
rs[id]=build(mid+1,r);
return id;
}
int update(int pre,int l,int r,int x){
int id=++cnt;
ls[id]=ls[pre];
rs[id]=rs[pre];
tree[id]=tree[pre]+1;
if(l==r) return id;
int mid=(l+r)>>1;
if(x<=mid) ls[id]=update(ls[pre],l,mid,x);
else rs[id]=update(rs[pre],mid+1,r,x);
return id;
}
int query(int LT,int RT,int l,int r,int k){
if(l>=r)
return l;
int mid=(l+r)>>1;
int sum=tree[ls[RT]]-tree[ls[LT]];
if(sum>=k) return query(ls[LT],ls[RT],l,mid,k);
else return query(rs[LT],rs[RT],mid+1,r,k-sum);
}
int main(){
int x,y,k;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
b[i]=a[i];
}
sort(b+1,b+n+1);
int s=unique(b+1,b+n+1)-b-1;
rt[0]=build(1,s);
for(int i=1;i<=n;i++){
int d=lower_bound(b+1,b+s+1,a[i])-b;
rt[i]=update(rt[i-1],1,s,d);
}
for(int i=1;i<=m;i++){
scanf("%d%d%d",&x,&y,&k);
printf("%d\n",b[query(rt[x-1],rt[y],1,s,k)]);
}
}
PS:主席树时间复杂度为O((n+m)logs),建树为O(nlogs),修改为O(logs),s为去重后的元素个数。
空间复杂度为O(nlogn+nlogn),空树O(nlogn),加树(logn)。
动态主席树待续...