1. 程式人生 > 實用技巧 >【演算法】 口胡主席樹

【演算法】 口胡主席樹

  “可持久化”幾個字限制我的想象力,導致我長久沒有去接觸這種強大的資料結構,主席樹的思想如下:

  線上段樹中,不難發現,每次修改與查詢的時間複雜度都是o(logn)的,這是因為每次操作所要呼叫與修改的節點也是logn個。

  在一些題目中,題目要求我們查詢線段樹(尤其是值域線段樹)的歷史版本,這就可以用到主席樹這一資料結構,每次修改都只新增logn個新節點,並記錄下來每次修改後根的位置,每次查詢都從某版本的根開始,就可以方便快捷的操作,同時把時間複雜度卡死在o(logn)。

   題目:區間第K大

  主席樹的經典題,運用一點字首和的思想。A1~A2,A1~A2......

A1~An每個區間建立起一棵值域線段樹。把陣列中每一個數看作插入操作,從頭到尾相當於建立了n個版本的主席樹。每次查詢的時候,一個區間內數的個數便可以用差分求出,然後用線段樹常規求第K大可以了。

  程式碼(糞山,LC原話):

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#define maxn 100005*160
#define lc(a1) t[a1].ch[0]
#define rc(a1) t[a1].ch[1]
#define sum(a1) t[a1].sum
#define
left(a1) t[a1].left #define right(a1) t[a1].right struct hjt_tree { int ch[2],sum,left,right; }t[maxn]; int a[maxn],b[maxn],n,m,size,rt[maxn]; void change(int,int,int); int query(int,int,int); void build(int,int,int); int main() { scanf("%d%d",&n,&m); for (int i = 1;i <= n;i++) scanf("
%d",&a[i]), b[i] = a[i]; std::sort(b + 1,b + n + 1); int len = std::unique(b + 1,b + n + 1) - b - 1; build(rt[0] = 0,1,n); for (int i = 1;i <= n;i++) { int loc = std::lower_bound(b + 1,b + len + 1,a[i]) - b; change(rt[i] = ++size,rt[i - 1],loc); } for (int i = 1;i <= m;i++) { int x,y,z; scanf("%d%d%d",&x,&y,&z); printf("%d\n",b[query(rt[x - 1],rt[y],z)]); } return 0; } void build(int x,int l,int r) { sum(x) = 0; left(x) = l; right(x) = r; if (l == r) return ; int mid = (l + r) / 2; build(lc(x) = ++size,l,mid); build(rc(x) = ++size,mid + 1,r); } void change(int x,int ori,int loc) { sum(x) = sum(ori) + 1; lc(x) = lc(ori); rc(x) = rc(ori); left(x) = left(ori); right(x) = right(ori); if (left(x) == right(x)) return ; int mid = (left(x) + right(x)) / 2; if (loc <= mid) change(lc(x) = ++size,lc(ori),loc); else change(rc(x) = ++size,rc(ori),loc); } int query(int x,int y,int k) { int know = sum(lc(y)) - sum(lc(x)); if (left(x) == right(x)) return left(x); int mid = (left(x) + right(x)) / 2; if (k <= know) return query(lc(x),lc(y),k); else return query(rc(x),rc(y),k-know); }