可持久化線段樹/主席樹(靜態)

參考博客c++

先介紹一下主席樹,主席樹也稱函數式線段樹也稱可持久化線段樹。(其實就是支持查詢歷史版本,這個在看完以後就會了解)編程

其實主席樹就是不少線段樹組合的整體,從它的其它稱呼也能夠看出來了,其實它本質上仍是線段樹。數組

主席樹就是利用函數式編程的思想來使線段樹支持詢問歷史版本、同時充分利用它們之間的共同數據來減小時間和空間消耗的加強版的線段樹。那麼它是怎麼實現的呢?函數式編程

好比有4個數5 3 6 9,求區間[2,4]第2小的數。函數

T[i]表示第i棵線段樹的根節點編號,L[i]表示節點i的左子節點編號,R[i]表示節點i的右子節點編號,sum[i]表示節點i對應區間中數的個數。
咱們先把序列離散化後是2 1 3 4。
我以前已經說了,主席樹就是不少線段樹的整體,而這些線段樹就是按給定序列的全部前綴創建的。從T[0]開始創建空樹,以後依次加入第i個數創建T[i]。
注意,若是咱們直接以序列的全部前綴創建線段樹確定會MLE,這裏主席樹最精妙的地方就出來了。咱們創建的這些線段樹的結構,維護的區間是相同的,主席樹充分利用了這些線段樹中的相同部分,大大減小了空間消耗,達到優化目的。優化

直接上圖,邊看圖邊理解上面的話。ui

 

圖中上面爲用序列全部前綴創建的線段樹,下面爲全部線段樹組合成主席樹。
圖中每一個節點上面爲節點編號,節點下面爲對應區間,節點中數爲區間中含有的數的個數,後面省略了區間。
從圖中應該能夠看出主席樹是怎麼充分利用這些線段樹的相同結構來減小空間消耗的。當要新建一個線段樹時最多隻須要新增log2nlog2n個節點,至關於只更新了一條鏈,其它節點與它的前一個線段樹公用。spa

建完主席樹後咱們看看它是怎麼查找區間[2,4]第2小的數的。
首先咱們要了解這些線段樹是可加減的,好比咱們要處理區間[l,r],那麼咱們只需處理sum[T[r]]-sum[T[l-1]]就是給定序列的區間[l,r]中的數的個數。由於咱們是按前綴處理的,這裏看圖本身體會一下。
這裏咱們要先計算res=sum[L[T[4]]]-sum[L[T[1]]]=1,即算出給定序列區間[2,4]中數的範圍在區間[1,2]的數的個數,若是它的值大於k那麼咱們就應該從線段樹的根節點走到左節點找第k個數,不然咱們就應該從根節點到右節點找第k-res個數,以後遞歸下去直到葉子節點,返回葉子節點對應區間即爲咱們查找的數在離散化後序列中的下標。這裏返回值爲3,對應離散化後序列中數3,即原序列中數6。.net

講到這裏,靜態的主席樹就講完了。咱們算算時空複雜度。
設原序列有n個數,含有m次詢問
空間複雜度:(建空樹)4*n+(前綴和更新)nlog2nlog2n
通常咱們數組大小就開nlog2nlog2n (動態不同,以後會講)
時間複雜度:mlog2ncode

給一道裸題: 【模板】可持久化線段樹 1(主席樹)

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define ll long long
 4 const int maxn = 2e6+5;
 5 ll tree[maxn],L[maxn<<2],R[maxn<<2],sum[maxn<<2];
 6 ll sz[maxn],h[maxn];
 7 ll n,m,al,ar,tot,k;
 8 int buf[17];
 9 inline void read(ll &x){
10     char ch=getchar(); x=0;
11     while(ch<'0') ch=getchar();
12     while(ch>='0' && ch<='9') x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
13 }
14 inline void write(int x){
15     if(!x){putchar('0');putchar(' ');return;}
16     register int cnt=0;
17     while(x)buf[++cnt]=(x%10)+48,x/=10;
18     while(cnt)putchar(buf[cnt--]);
19     putchar('\n');
20 }
21 inline void build(ll &root,ll l,ll r){
22     root = ++tot;
23     sum[root] = 0;
24     if(l == r)return;
25     ll mid = l+r>>1;
26     build(L[root],l,mid);
27     build(R[root],mid+1,r);
28 }
29 inline void update(ll &root,ll l,ll r,ll pre,ll x){
30     root = ++tot;
31     L[root] = L[pre];
32     R[root] = R[pre];
33     sum[root] = sum[pre]+1;
34     if(l == r)return;
35     ll mid =l+r>>1;
36     if(x <= mid)update(L[root],l,mid,L[pre],x);
37     else update(R[root],mid+1,r,R[pre],x);
38 }
39 inline ll query(ll s,ll e,ll l,ll r,ll k){
40     if(l == r)return l;
41     ll mid = l+r>>1;
42     int res = sum[L[e]]-sum[L[s]];
43     if(k <= res)return query(L[s],L[e],l,mid,k);
44     else return query(R[s],R[e],mid+1,r,k-res);
45 }
46 int main(){
47     read(n);
48     read(m);
49     for(register int i = 1;i <= n;i++)read(sz[i]),h[i] = sz[i];
50     sort(h+1,h+1+n);
51     int num = unique(h+1,h+1+n)-h-1;
52     tot = 0;
53     build(tree[0],1,num);
54     for(register int i = 1;i <= n;i++)
55         update(tree[i],1,num,tree[i-1],lower_bound(h+1,h+1+num,sz[i])-h);
56     while(m--){
57         read(al);
58         read(ar);
59         read(k);
60         int ans = query(tree[al-1],tree[ar],1,num,k);
61         write(h[ans]);
62     }
63     return 0;
64 }
相關文章
相關標籤/搜索