可持久化线段树可以将历史版本的线段树记忆下来,并支持新建版本与查询历史版本。
其中有一道经典的静态主席树的题就是求区间k大。
建树的过程其实就是先建一棵空树,再按输入顺序插入节点。需要新开节点的时候就新开,如果和之前没有什么变化的话就连到之前的树上。这样的空间复杂度据说是Θ(nlogn)
由于线段树支持区间减法,于是我们可以在查询区间的时候利用前缀和的思想,对于[L,R]的区间,我们可以让第L-1个线段树与第R个线段树相减。
代码如下:(附有较详细的注释。)
1 //Writer : Hsz %WJMZBMR%tourist%hzwer 2 #include <iostream> 3 #include <cstring> 4 #include <cstdio> 5 #include <cmath> 6 #include <queue> 7 #include <map> 8 #include <set> 9 #include <stack> 10 #include <vector> 11 #include <cstdlib> 12 #include <algorithm> 13 #define LL long long 14 using namespace std; 15 const int N=200005<<5; 16 int l[N],r[N],sum[N],b[N];//sum表示线段树区间内点的数量。 17 int n,m,a[N],root[N],tot; 18 int build(int L,int R) { 19 int rt=++tot; 20 if(L<R) { 21 l[rt]=build(L,(L+R)>>1);//建一个空树。l[rt]表示rt的左儿子。 22 r[rt]=build((L+R)/2+1,R); 23 } 24 return rt; 25 } 26 int update(int pre,int L,int R,int c) {//加点,把历史版本记录下来。 27 int rt=++tot; 28 l[rt]=l[pre],r[rt]=r[pre],sum[rt]=sum[pre]+1;//pre:上一棵树的根,因为插入了一个新的点,所以sum肯定比上一个多1。 29 if(L<R) { 30 if(c<=((L+R)>>1))l[rt]=update(l[pre],L,(L+R)>>1,c);//如果插入的点在这个线段树的左子树,就递归到左子树。 31 else r[rt]=update(r[pre],(L+R)/2+1,R,c);//同理。 32 } 33 return rt;//返回新建子树的根节点。 34 } 35 int query(int u,int v,int L,int R,int k) { 36 if(L==R) return L; 37 int x=sum[l[v]]-sum[l[u]];//u:区间左端点的线段树的根,v:右端点的线段树的根。 38 //如果左子树的增加的点数仍旧大于要求的k,那么就递归到左子树求。 39 if(x>=k) return query(l[u],l[v],L,(L+R)>>1,k); 40 //否则递归到右子树,减去左子树上的点个数。 41 else return query(r[u],r[v],(L+R)/2+1,R,k-x); 42 } 43 int main() { 44 cin>>n>>m; 45 for(int i=1; i<=n; i++) scanf("%d",&a[i]),b[i]=a[i]; 46 sort(a+1,a+1+n); 47 int u=unique(a+1,a+1+n)-a-1; 48 root[0]=build(1,u); 49 for(int i=1; i<=n; i++) { 50 b[i]=lower_bound(a+1,a+1+u,b[i])-a;//离散化。 51 root[i]=update(root[i-1],1,u,b[i]); 52 } 53 int q,v,k; 54 for(int i=1; i<=m; i++) { 55 scanf("%d%d%d",&q,&v,&k); 56 printf("%d ",a[query(root[q-1],root[v],1,u,k)]); 57 } 58 return 0; 59 }