主席樹真是神奇的物種!
題意:給n、m
下面有n個數 (編號1到n)
有m個詢問,詢問的是上面的數的編號在[l,r]之間第k小的數
n、m的范圍都是$10^5$
是主席樹的入門題
借此來學習一下主席樹
主席數利用函數式線段樹來維護數列,一般用來解決區間第k大問題
空間時間的復雜度小於樹套樹(常數小)
划分樹也可以解決區間第k大問題,但划分樹不支持修改,主席樹可以(用樹狀數組維護)
(這三道入門題都是無修改的)
我們先來YY一下這種求區間第k(大)小的題目···
最容易想到的做法就是對於每個詢問,對[l, r]區間排個序,輸出第k小
這樣的復雜度是O($m\times nlogn$)
大家都很容易想到排序,但是對於每個詢問每個區間排序的代價太大了...
再想想,讓我們加入一些線段樹的思想,
要求第k小,也就是與個數相關,那么我們可以 以[l,r]區間內的數的個數來建立一棵線段樹
結點的值是數的個數,當我們要找第k小的數時,若左子樹大於k,那么很顯然第k小的數在左子樹中;若左子樹小於k,那么第k小的數在右子樹中
建樹的復雜度是O(nlogN),查詢的復雜度是O(logN) (這里的N是不相同數的數量)
若我們仍對每個查詢建樹,那么復雜度絲毫沒有降低(反而提高了),那有沒有什么辦法可以不要每次查詢都建樹呢?
(讓我們聯想一下前綴和) 假設我們知道[1, l-1]之間有多少個數比第k小的數小,那么我們只要減去這些數之后在[1, r]區間內第k小的數即是[l, r]區間內的第k小數
更確切的說,我們要求[l, r]區間內的第k小數 可以 用以[1, r]建立的線段樹去減去以[1, l-1] 建立的線段樹
這樣能夠減的條件是這兩棵樹必須是同構的。
若是不太明白, 我們來舉個例子:
如有序列 1 2 5 1 3 2 2 5 1 2
我們要求 [5,10]第5小的數
(數列中不存在4、6、7、8 但根據原理就都寫出來了,為方便理解,去掉了hash的步驟,實際的代碼中其實只要一棵4個葉子節點的樹即可)
(紅色的為個數)
我們建立的[1, l-1] (也就是[1, 4])之間的樹為
[1, r]也就是[1, 10]的樹為
兩樹相減得到
我們來找第5小的數:
發現左子樹為5 所以第5小的數在左邊, 再往下(左4右1) 發現左邊小於5了 ,所以第5小的數在右邊 所以第5小的數就是3了
同樣的,我們只要建立[1, i] (i是1到n之間的所有值)的所有樹,每當詢問[l, r]的時候,只要用[1, r]的樹減去[1, l-1]的樹,再找第k小就好啦
我們將這n個樹看成是建立在一個大的線段樹里的,也就是這個線段樹的每個節點都是一個線段樹( ——這就是主席樹)
最初所有的樹都是空樹,我們並不需要建立n個空樹,只要建立一個空樹,也就是不必每個節點都建立一個空樹
插入元素時,我們不去修改任何的結點,而是返回一個新的樹( ——這就是函數式線段樹)
因為每個節點都不會被修改,所以可以不斷的重復用,因此插入操作的復雜度為O(logn)
總的復雜度為O((n+m)lognlogN) (聽說 主席樹的芭比說 加上垃圾回收, 可以減少一個log~~~ 然而這只是聽說)
你以為這樣就結束了嗎!!
你沒有發現這樣空間大到爆炸嗎!!!
你在每個節點都建了一個線!段!樹!這不MLE才有鬼呢!!!
那怎么辦呢?
$T_i$表示一棵[1, i]區間的線段樹
那么$T_i$與$T_{i-1}$的區別就只有當前插入的這個元素$a_i$以及它的父親以及他父親的父親以及他父親的父親的父親...
也就是改變的就只有他和他上面logn個數
所以,我們並不需要建一整棵樹,我們只需要 單獨建立logn個結點,跟$T_{i-1}$連起來就好了
這樣樹的空間復雜度(NlogN)
以下是代碼:

1 #define lson l, m 2 #define rson m+1, r 3 const int N=1e5+5; 4 int L[N<<5], R[N<<5], sum[N<<5]; 5 int tot; 6 int a[N], T[N], Hash[N]; 7 int build(int l, int r) 8 { 9 int rt=(++tot); 10 sum[rt]=0; 11 if(l<r) 12 { 13 int m=(l+r)>>1; 14 L[rt]=build(lson); 15 R[rt]=build(rson); 16 } 17 return rt; 18 } 19 20 int update(int pre, int l, int r, int x) 21 { 22 int rt=(++tot); 23 L[rt]=L[pre], R[rt]=R[pre], sum[rt]=sum[pre]+1; 24 if(l<r) 25 { 26 int m=(l+r)>>1; 27 if(x<=m) 28 L[rt]=update(L[pre], lson, x); 29 else 30 R[rt]=update(R[pre], rson, x); 31 } 32 return rt; 33 } 34 35 int query(int u, int v, int l, int r, int k) 36 { 37 if(l>=r) 38 return l; 39 int m=(l+r)>>1; 40 int num=sum[L[v]]-sum[L[u]]; 41 if(num>=k) 42 return query(L[u], L[v], lson, k); 43 else 44 return query(R[u], R[v], rson, k-num); 45 } 46 47 int main() 48 { 49 // int t; 50 // scanf("%d", &t); 51 // while(t--) 52 // { 53 tot=0; 54 int n, m; 55 scanf("%d%d", &n, &m); 56 for(int i=1; i<=n; i++) 57 { 58 scanf("%d", &a[i]); 59 Hash[i]=a[i]; 60 } 61 sort(Hash+1, Hash+n+1); 62 int d=unique(Hash+1, Hash+n+1)-Hash-1; 63 T[0]=build(1, d); 64 for(int i=1; i<=n; i++) 65 { 66 int x=lower_bound(Hash+1, Hash+d+1, a[i])-Hash; 67 T[i]=update(T[i-1], 1, d, x); 68 } 69 while(m--) 70 { 71 int l, r, k; 72 scanf("%d%d%d", &l, &r, &k); 73 int x=query(T[l-1], T[r], 1, d, k); 74 printf("%d\n", Hash[x]); 75 } 76 // } 77 }