1. 程式人生 > >可持久化線段樹(主席樹)

可持久化線段樹(主席樹)

AS string can -a 過程 思想 oot and amp

關於可持久化線段樹

可持久化線段樹可以將歷史版本的線段樹記憶下來,並支持新建版本與查詢歷史版本。

其中有一道經典的靜態主席樹的題就是求區間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\n",a[query(root[q-1],root[v],1,u,k)]); 57 } 58 return 0; 59 }

可持久化線段樹(主席樹)