1. 程式人生 > >主席樹學習筆記

主席樹學習筆記

using 鏈表 自然 bit namespace \n root getch bool

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>

using
namespace 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); } }

後面有空再填

主席樹學習筆記