可持久化線段樹node
什麼是可持久化線段樹?ios
即主席樹,能夠維護區間的第k大,據說能夠維護動態區間第k大,也能夠維護靜態區間第k大,可是我太菜了!只會靜態區間第k大。數組
爲何要叫主席樹?據說是某個大佬發明的,只是由於他不懂劃分樹,而後就發明了這種樹,由於跟某主席同名,因此被叫作了主席樹,ORZ。優化
其實,我以爲可持久化線段樹的原理跟前綴和的思想是差很少的,前綴和就是一個狀態一個狀態的進行保留,那麼若是線段樹的每一個狀態都獲得了保留,那麼咱們是能夠舉出任何兩個狀態之間的線段樹,就能夠搞定區間第k大的詢問了!ui
那麼咱們要先了解一個東西,好比單棵線段樹如何求區間第k大?每一個底層節點表明1,2,3,4,……..,表明第一小,第二小,第三小 ………..,其中節點的sum值表明這個值的出現次數,那麼咱們只要從根節點出發,判斷左節點的sum 是否小於 k,若是是小於的話,那麼第k大確定在右節點裏面,若是是大於的話那麼第k大確定是在右節點裏面,只要往右邊跑就對了,只時候k = k - 右子樹的sum,再往下跑,直到找到符合的節點,即第k小。spa
講完了這個東西,那咱們應該來看一個東西,即如何將單棵線段樹,合併起來,你可能會說單棵線段樹,一棵一棵保留起來,這樣就能夠了。。。。。可是咱們要先考慮一下,這樣去建可持久化線段樹確定是會MLE的。因此咱們要考慮一下如何優化空間使得線段樹能夠放得下,其實咱們能夠發現每次線段樹的更新,只會更新一條路,那麼咱們只要把這條路保留起來就能夠了,就是把這個狀態保留起來就OK了。其餘的不更新的樹,鏈接到原來的前一棵樹就OK了。3d
如圖:code
那麼咱們要如何進行查詢操做呢?blog
咱們只要將兩棵線段樹的狀態進行相減就能夠獲得一個符合的狀態的線段樹,排序
就變成了單棵線段樹進行查詢第k大!
附上代碼 例題poj 2104
#include <iostream> #include <cstdio> #include <cstring> #include <vector> #include <algorithm> using namespace std; vector<int>vec; const int MAXN = 100005; int arr[MAXN]; int root[MAXN];///該數組儲存根節點的位置 typedef struct NODE { int l, r, sum; /** 如今的l r 已經再也不是單純的rt << 1 , rt << 1 | 1 而是指向,指向節點 **/ }node; node segtree[MAXN * 20]; int gets(int x) { return lower_bound(vec.begin(), vec.end(), x) - vec.begin() + 1; } int tot; int build(int l, int r)///return p p的值是點的座標 { int p = ++tot; segtree[p].sum = 0; if(l == r) { /** 一層一層的返回就對了。。。。。。 返回這個節點的座標就對了。。。。。 而後才能實現 l r 的指向 **/ return p;///返回指向 } int mid = (l + r) >> 1; segtree[p].l = build(l, mid); segtree[p].r = build(mid + 1, r); return p; } int update(int now, int l, int r, int pos, int val) /** now - 當前節點 pos - 指要更新的點 val - 權值 每次更新,都會建立一條鏈,因此得更新tot **/ { int p = ++ tot; segtree[p] = segtree[now];///這樣會將上一個的點 ///的值賦值給當前的點,這樣不須要修改的那棵子樹的鏈接 ///這樣每次多生成的一顆線段樹,只要多一條鏈,因此節省了空間 if(l == r) { segtree[p].sum += val;///若是每次更新到底的話,那麼就更新底的值 return p; } int mid = (l + r) >> 1; if(pos <= mid)///若是增長的點 < mid 那麼確定能夠往左子樹移動 { segtree[p].l = update(segtree[p].l, l, mid, pos, val); } else///不然向右子樹移動 { segtree[p].r = update(segtree[p].r, mid + 1, r, pos, val); } segtree[p].sum = segtree[segtree[p].l].sum + segtree[segtree[p].r].sum; return p; } int query(int p, int q, int l, int r, int k) { if(l == r) return l;///查詢到底就輸出 int mid = (l + r) >> 1; int cnt = segtree[segtree[p].l].sum - segtree[segtree[q].l].sum; ///將兩棵樹相減,可得要的狀態 if(k <= cnt)///k 即爲第k小,那麼就是查詢第k小,判斷個數再進行移動 { return query(segtree[p].l, segtree[q].l, l, mid, k); } else { return query(segtree[p].r, segtree[q].r, mid + 1, r, k - cnt); } } int main() { int n,q; while(~scanf("%d%d",&n,&q)) { memset(arr, 0, sizeof(arr)); vec.clear(); tot = 0; for (int i = 1; i <= n; i ++) { scanf("%d",&arr[i]); vec.push_back(arr[i]); } sort(vec.begin(), vec.end()); vec.erase(unique(vec.begin(),vec.end()),vec.end()); ///排序去重,離散處理 root[0] = build(1, n); ///printf("tot = %d\n",tot); ///先建樹 for (int i = 1; i <= n; i ++) { root[i] = update(root[i - 1], 1, n, gets(arr[i]), 1); } int a, b, c; for (int i = 0; i < q; i ++) { scanf("%d%d%d",&a,&b,&c); int re = query(root[b], root[a - 1], 1, n, c); //printf("re = %d\n",re); printf("%d\n",vec[re - 1]); } } return 0; }