[主席樹]HDOJ2665 && POJ2104 && POJ2761


主席樹真是神奇的物種!

Orz一篇資料

題意:給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 }
POJ 2104 && HDOJ 2665 && POJ 2761

 

 

能修改的戳這里~~~~~

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM