【POJ 2104】【主席樹模板題】K-th Number
阿新 • • 發佈:2018-11-19
題意:
靜態詢問區間第K大問題。給出一個數組,然後多次詢問某一區間第K大數是多少。
思路:
典型的主席樹模板題。
所以就大致講一下靜態主席樹的原理。
先回顧一下權值線段樹,每個節點維護一個sum,表示陣列中有多少個點在這個節點所代表的左右區間內,這樣就可以求出全域性第k大問題。
那麼主席樹就是建立多棵線段樹,第i個線段樹維護了 [1-i] 區間內的權值線段樹,那麼當求 [l,r] 這個區間內的第k大時,就只需要考慮 第l-1 和 第r 棵線段樹,將這兩個線段樹內相同節點的sum相減,就是[l,r]這個區間內的資訊了。
然後因為第i棵線段樹和第i+1棵線段樹中有大量公用部分,所以每多建一棵線段樹,只需要多開logn個節點,實現了空間優化。
程式碼:
#include <cstdio> #include <iostream> #include <cstring> #include <algorithm> #define rep(i,a,b) for(int i = a; i <= b; i++) using namespace std; const int N = 1e5+100; struct Tree{ int lc,rc; //左右節點 t陣列 編號 int l,r; //節點的左右端點 int sum; }t[N*20]; int n,m,a[N],num,b[N],root[N],tot; int build(int l,int r) { int p = ++tot; // 新建一個節點,編號為p,代表當前區間[l,r] t[p].l = l, t[p].r = r, t[p].sum = 0; if(l == r) return p; int mid = (l+r)>>1; t[p].lc = build(l,mid); t[p].rc = build(mid+1,r); return p; } int insert(int now,int pos,int k) { int p = ++tot; t[p] = t[now]; //建立副本 if(t[p].l == t[p].r){ t[p].sum += k; //在副本上修改 return p; } int mid = (t[p].l+t[p].r)>>1; if(pos <= mid) t[p].lc = insert(t[p].lc,pos,k); //保留右兒子部分,把左兒子更新 else t[p].rc = insert(t[p].rc,pos,k); t[p].sum = t[t[p].lc].sum + t[t[p].rc].sum; return p; } int ask(int lp,int rp,int k) //lp和rp所代表的區間是相同的,他們只不過是在不同狀態下的副本 { if(t[lp].l == t[lp].r) return t[lp].l; //找到答案 int cnt = t[t[rp].lc].sum-t[t[lp].lc].sum; // 值在[l,mid]中的數有多少個 if(cnt >= k) return ask(t[lp].lc,t[rp].lc,k); else return ask(t[lp].rc,t[rp].rc,k-cnt); } int main() { num = tot = 0; scanf("%d%d",&n,&m); rep(i,1,n){ scanf("%d",&a[i]); b[++num] = a[i]; } sort(b+1,b+1+num); //離散化 num = unique(b+1,b+1+num)-b-1; root[0] = build(1,num); //root[0]這顆樹是一棵空樹,關於離散化後的值域建樹 rep(i,1,n) { int x = lower_bound(b+1,b+1+num,a[i])-b; //離散化後的值 root[i] = insert(root[i-1],x,1); //值為x的數增加1個 } rep(i,1,m) { int x,y,z; scanf("%d%d%d",&x,&y,&z); int ans = ask(root[x-1],root[y],z); printf("%d\n",b[ans]); //從離散化後的值變回原值 } return 0; //root[i]:表示只考慮1-i這些數時候建樹的情況,這顆樹樹根的編號 } /* 新建多個權值線段樹副本,記錄只考慮1-i個數時,每個數出現在各個區間的個數是多少,類似於建多棵權值線段樹 然後第i棵線段樹,參考第i-1棵線段樹,優化空間 空間為4*n*log(n) */