主席樹學習筆記
Part I 靜態主席樹
定義
主席樹最基礎可以維護區間K大的問題,由於其本質是可持久化線段樹,所以要對線段樹有很深的理解。
栗子:區間第K小
首先這種處理區間的問題肯定要想到區間數據結構。顯然如果是指定了區間,可以把讀入的數據離散化,然後建一顆值域線段樹。
但是要在任意的[l,r]中查詢第k小,一些大神就想到了前綴和。
首先建N顆線段樹,第i棵維護區間a1-ai的每個數的出現個數。
此時值域線段樹的結構都要保證完全相等,這樣這些線段樹就具有了可減性,就可以用前綴和維護了。
那這樣就要建N棵線段樹,空間無法承受。
我們可以輕易發現,維護a1-ai和a1-ai+1的線段樹,的每一個非葉節點的子樹有一半的結構都是相等的,如果能夠只修改一半,空間的問題就會解決。
那如何處理空間了
比如說對於一個數1926,817,1989,604,首先離散化
於是 1926 就等價於3,以此類推。
那按照剛才的思路,我們可以先建一棵樹維護a1-a1
如下圖
圓圈中的數字代表線段樹維護的東西,也就是在這個區間內的數有多少個。
這個時候我們考慮建a1-a2的線段樹,如果重新建這棵樹會變成這樣
對比前一棵樹,我們發現幾乎一半的結構都是相同的。(圈出來的即為相同)
那我們每次先新建一個根節點:
其中維護a1-ai的根節點為rooti,如下圖
我們每一棵節點都要記錄他左右兒子的編號(後面會解釋)。
這樣我們可以用讓root2的右兒子的編號等於root1右兒子的編號相同的方式使root2的右兒子等於root1的左兒子且節約空間
個人感覺有點像鏈表:
這樣要修改的節點就大大減少了。
至於為什麽要記錄每個節點的左右兒子編號,正是因為主席樹有一半的結構來源與前一棵樹,這是這個節點與先前那個節點不構成線段樹中i*2和i*2+1的關系
對於查詢,我們要利用到前綴和:
對於查詢,首先對於這棵子樹查詢K大:
我們只需要關註這棵子樹的左子樹出現的數的個數總和與K的大小關系:
如果小的話:在左子樹查K小
如果大的話:往右子樹查(k-左子樹總和)小
那怎麽知道L-R的個數:
前綴和,首先結構相同自然可以直接減啦!
至此靜態主席樹求靜態第K小就搞定了,下面結合代碼(非常醜陋):
#include<bits/stdc++.h> usingnamespace std; const int MAXN = 2 * 100000 + 10; inline int read() { int f = 1 ,x = 0; char ch; do { ch =getchar(); if(ch==‘-‘) f = -1; }while(ch<‘0‘||ch>‘9‘); do { x=(x<<3)+(x<<1)+ch-‘0‘; ch = getchar(); }while(ch>=‘0‘&&ch<=‘9‘); return f*x; } int n,m; struct node { int val; int id; friend bool operator < (node a1,node a2) { return a1.val<a2.val; } }; node a[MAXN]; int c[MAXN]; struct Tree { int lc; int rc; int sum; Tree() { sum = 0; } }; Tree tree[MAXN*20]; int root[MAXN]; int cnt =0; inline void init() { root[0]=0; tree[0].lc=tree[0].rc=0; tree[0].sum=0; return; } inline void update(int num,int &rt ,int l,int r) { tree[++cnt]=tree[rt]; rt = cnt; tree[rt].sum++; if(l==r) return; int mid = (l+r)>>1; if(num<=mid) update(num,tree[rt].lc,l,mid); else update(num,tree[rt].rc,mid+1,r); } inline int query(int l,int r,int k,int x,int y) { int now = tree[tree[r].lc].sum-tree[tree[l].lc].sum; if(x==y) return x; else { int mid = (x+y)>>1; if(now>=k) { return query(tree[l].lc,tree[r].lc,k,x,mid); } else return query(tree[l].rc,tree[r].rc,k-now,mid+1,y); } } int main() { n = read(); m = read(); for(int i=1;i<=n;i++) a[i].val=read(),a[i].id = i; sort(a+1,a+n+1); for(int i=1;i<=n;i++) { c[a[i].id]=i; } init(); for(int i=1;i<=n;i++) { root[i]=root[i-1]; update(c[i],root[i],1,n); } for(int i=1;i<=m;i++) { int L =read(); int R = read(); int k=read(); printf("%d\n",a[query(root[L-1],root[R],k,1,n)].val); } }
後面有空再填
主席樹學習筆記