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

「主席樹」學習筆記

== 傳說 比較 返回值 普通 哪裏 表數 成功 可能

主席樹

主席樹——可持久化線段樹。話說這個名字的來歷也非常有意思,傳說是一位非常非常巨的巨佬發明的,他的名字叫做黃嘉泰。由於名字縮寫和一位竹席的縮寫一模一樣,於是就叫主席樹了……

所謂可持久化線段樹,就是可以查詢歷史更新信息的線段樹。例如我對線段樹進行了5次更新,但是需要查詢第2次更新結束後的結果……一種顯而易見的做法是每次更新重新建樹。但是這樣的空間不知爆到哪裏去了……

其實主席樹的做法也是比較天然的。考慮一次單點更新只會對線段樹的一串節點(\(logn\)個)做出修改,剩下的節點依然沒有改變。因此考慮每一次添加一批節點,使這批節點依附在原始的線段樹上。

實現

在普通的線段樹中,我們按照規則:左子樹的編號為父親2,右子樹編號為父親

2+1. 然而這個規則在主席樹裏就不再適用了,因為為了充分利用空間,一個父親可能對應多個兒子。因此我們需要專門記錄每個節點的左右兒子編號

同時,由於根節點維護的是總體信息。因此每次更新時,根節點一定有所改變。所以我們可以每次記錄一下,第\(i\)次更新後的線段樹的更節點編號。記為\(T[i]\)。然而只要知道根節點編號,就能夠一步一步找到其對應的線段樹。一個根節點對應一個狀態的線段樹,不同的狀態的線段樹可能有公共節點。

在代碼實現上,我們先假設其左右兒子不做更改,然後再一個一個改變下去。

int update(int pre, int L, int R, int x){
    //pre表示更新前那個狀態的線段樹,[L,R]表示當前節點維護的區間,x表示當前更新的值 
    int cur = ++numNode;
    l[cur] = l[pre], r[cur] = r[pre], sum[cur] = sum[pre] + 1;
    if(L < R){
        if(x <= (L+R)/2) l[cur] = update(l[pre], L, (L+R)/2, x);
        else r[cur] = update(r[pre], (L+R)/2+1, R, x);  
        //直接改變左右兒子的編號。註意一定只改一個 
    }
    return cur;//這裏包括L==R的情況 
}

註意這裏的\(update\)函數是有返回值的,返回更新成功後新的那一個節點的編號。

靜態區間第\(K\)

采用可持久化權值線段樹。

權值線段樹查詢總體第\(K\)小並不難,和平衡樹查詢第\(K\)小非常相似。先禮散花,然後利用權值線段樹維護每個區間(葉子當然是從小到大的權值了)的數字個數。查詢時只需要從根節點往下走,每次比較其左兒子出現個數是否大於或等於\(K\)。如果左兒子中的數字個數都已經\(\geq K\)了,往左兒子走即可。否則就往右兒子走,記得此時應當繼續查詢第\(K-sum[lson]\)

註意,權值線段樹中葉子節點\([x,x]\)的值即代表數字\(x\)出現的次數

好了,現在考慮如何查詢區間。我們發現要求第\(K\)

大,實際上就是要求出每個區間內數字的個數。那麽現在加入要求區間\([L,R]\)內的數字個數,即為\([1,R]-[1,L-1]\)的數字個數。我們可以對於原序列,一個一個數字插入主席樹。從左往右,每次更新一個數字。這樣的話,區間\([L,R]\)內出現的數字個數就等同於當前線段樹的狀態,去減去第\(L-1\)次更新時的線段樹狀態。這就要用到可持久化線段樹了。

於是問題迎刃而解

剩余部分待更…………………………

「主席樹」學習筆記