♥可持久化線段樹(函數式線段樹):
可持久化數據結構(Persistent data structure)就是利用函數式編程的思想使其支持詢問歷史版本、同時充分利用它們之間的共同數據來減少時間和空間消耗。
所以這里講的可持久化線段樹也叫函數式線段樹(又叫主席樹……因為先驅就是fotile主席Orz……)。偶還是比較喜歡叫它函數式線段樹。
兩篇論文:
《范浩強_wc2012談談各種數據結構》 ---范浩強
《可持久化數據結構研究》 ---陳立傑
OOOOOrrrzz…………
兩篇博客:
http://seter.is-programmer.com/posts/31907.html 很詳細的介紹了函數式線段樹(主席樹)。
http://fotile.is-programmer.com/posts/31250.html 主席親筆啊 Orz……
反正大體的思想就是只賦值不修改,保存歷史版本------每次加入新結點后都返回一顆包含新結點的新樹保存起來(什么?這么多樹空間消耗太太太大?------充分利用歷史版本)
可用函數式線段樹做的題目:POJ2104; SPOJ COT,COT2,COT4; BZOJ 1901(ZJU 2112); BZOJ2653; HDOJ 4417; HDOJ4348。
♣POJ 2104 K-th Number (HDU 2665)(不帶修改的區間第k小值)
首先來看 線段樹找第k小:假設數是在(0,n)之間的,對權值建一顆線段樹,每個節點用cnt表示這個權值上的數的個數。那么考慮在當前節點t內找t節點內的第k小。如果t的左兒子上數的個數cnt已經大於k,那么第k小一定在左兒子中,所以就在左兒子中找第k小數。否則,答案就是在右兒子中找第k-cnt(leftson)小的數。
現在來考慮區間第k小。直接用上面的方法顯然是不行的,因為可能區間和節點會有交叉(區間一部分占一個節點的一部分,另一部分占另一個節點的一部分)。
預備知識:兩顆結構相同的權值線段樹a,b,權值線段樹a(+/-)b的每個節點的cnt值就是對應權值線段樹a的cnt值減去b的cnt值。那么對於權值線段樹a-b,我們想對它查詢並不需要建出它,只需要在a、b中對應的位置分別維護即可。
開始求區間第k小:先對所有數離散化處理,然后用ati表示對a0,a1,...,ai-1按上面說的方法建權值線段樹。那么ati可以通過ati-1添加一個位置得到。那么得到所有的ati只需要O(nlogn)的時間和空間。那么要詢問區間al+1,al+2,...,ar的第k小數,只需要在線段樹atr-atl上找第k小數就可以了。算法復雜度O(nlogn) +O(logn) +O(nlogn)。

1 #include <iostream> 2 #include <cstdio> 3 #include <cstdlib> 4 #include <cmath> 5 #include <vector> 6 #include <stack> 7 #include <queue> 8 #include <map> 9 #include <algorithm> 10 #include <string> 11 #include <cstring> 12 #define MID(x,y) ((x+y)>>1) 13 14 using namespace std; 15 16 const int MAXN = 100010; 17 struct tree 18 { 19 int l,r; 20 int ls,rs; 21 int sum; 22 }t[MAXN*20]; 23 int tot,root[MAXN]; 24 25 int build(int l, int r) 26 { 27 int k = ++ tot; 28 t[k].l = l, t[k].r = r; 29 t[k].sum = 0; 30 if (l == r) return k; 31 int mid = MID(l,r); 32 t[k].ls = build(l,mid); 33 t[k].rs = build(mid+1,r); 34 return k; 35 } 36 int change(int o, int x, int v) 37 { 38 int k = ++ tot; 39 t[k] = t[o]; 40 t[k].sum += v; 41 if (t[o].l == x && t[o].r == x) 42 return k; 43 int mid = MID(t[o].l, t[o].r); 44 if (x <= mid) t[k].ls = change(t[o].ls, x, v); 45 else t[k].rs = change(t[o].rs, x, v); 46 return k; 47 } 48 int query(int n,int o,int k) //詢問區間[t1,t2]第k小 49 { 50 if (t[n].l == t[n].r) return t[n].l; 51 int res = t[t[n].ls].sum-t[t[o].ls].sum; 52 if (k <= res) 53 return query(t[n].ls,t[o].ls,k); 54 else return query(t[n].rs,t[o].rs,k-res); 55 } 56 57 int b[MAXN],sortb[MAXN]; 58 int q; 59 int main() 60 { 61 //freopen("test.in","r+",stdin); 62 int n,m; 63 while(scanf("%d%d",&n,&m)!=EOF) 64 { 65 for (int i = 1; i <= n; i ++) 66 scanf("%d",&b[i]),sortb[i]=b[i]; 67 sort(sortb+1, sortb+n+1); 68 int i; 69 for (q = 1, i = 2; i <= n; ++ i) 70 if (sortb[q] != sortb[i]) 71 sortb[++q] = sortb[i]; 72 root[0] = build(1,q); 73 for (int i = 1; i <= n; i ++) 74 { 75 int p = lower_bound(sortb+1, sortb+n+1, b[i])-sortb; 76 root[i] = change(root[i-1], p, 1); 77 } 78 79 for (int i = 0; i < m; i ++) 80 { 81 int a,b,k; 82 scanf("%d%d%d",&a,&b,&k); 83 printf("%d\n",sortb[query(root[b],root[a-1],k)]); 84 } 85 86 } 87 return 0; 88 }
PS: 這個可以歸納為一類問題 --> 當我們在權值線段樹上可以完成某些對整個區間的操作而對子區間無力時,可以用這種函數式線段樹的轉換方法來完成,但需要滿足一個條件:線段樹中保存的數據要支持區間減法(就是諸如區間[l,r]可以通過區間[1,r] - [1,l-1]來算出)。我曾想過用這種函數式線段樹的方法做HDU 4358,結果寫完調試發現不對就是因為那題不滿足區間減法。。。囧。當然我還是覺得函數式線段樹是可以做那題的,只不過我對這個還不太深入掌握(說白了就是能力捉雞T_T。。。)
(未完待續...)