1. 程式人生 > >[可持久化線段樹(主席樹)]

[可持久化線段樹(主席樹)]

要求 als 長度 span else -s d+ 左右 教室

主席樹

拋出問題

如題,給定N個整數構成的序列,將對於指定的閉區間查詢其區間內的第K小值。

輸入輸出格式

輸入格式:

第一行包含兩個正整數N、M,分別表示序列的長度和查詢的個數。

第二行包含N個整數,表示這個序列各項的數字。

接下來M行每行包含三個整數l, r, kl,r,k , 表示查詢區間[l, r][l,r]內的第k小值。

輸出格式:

輸出包含k行,每行1個整數,依次表示每一次查詢的結果

解決問題

主席樹(可持久化線段樹)法

於是針對這個問題,新的數據結構誕生了,也就是主席樹。
主席樹本名可持久化線段樹,也就是說,主席樹是基於線段樹發展而來的一種數據結構。其前綴”可持久化”意在給線段樹增加一些歷史點來維護歷史數據,使得我們能在較短時間內查詢歷史數據,圖示如下。

技術分享圖片

圖中的橙色節點為歷史節點,其右邊多出來的節點是新節點(修改節點)。
下面我們來講怎麽構建這個數據結構。

主席樹教程

  • 要求:掌握線段樹這個數據結構。
  • 註意:一般主席樹一類的題目,難的不是寫主席樹,而是主席樹的運用。

主席樹的點修改

不同於普通線段樹的是主席樹的左右子樹節點編號並不能夠用計算得到,所以我們需要記錄下來,但是對應的區間還是沒問題的。

//節點o表示區間[l,r],修改點為p,修改值根據題意設定(此處我們先不談題目,只談數據結構)
int modify(int o, int l, int r, int p)
{
    int oo = ++node_cnt;
    lc[oo] 
= lc[o]; rc[oo] = rc[o]; sum[oo] = sum[o] + 1;//新節點,這裏是根據模板題來的 if(l == r)//遞歸底層返回新節點編號,修改父節點的兒子指向 { //sum[oo] = t;如果題目要求sum是加t的再這樣弄,然後上面的+1就去掉 return oo; } int mid = (l + r) >> 1; if(p <= mid) lc[oo] = modify(lc[oo], l, mid); else rc[oo] = modify(rc[oo], mid+1
, r); //sum[oo] = sum[lc[oo]] + sum[rc[oo]];在該題中,不需要這樣做,但是很多情況下是要這樣更新的 return oo; }

至於主席樹的區間修改,其實也不難,但是復雜度有點高,簡單點的題目一般只有點修改,有時候區間修改可以轉化為點修改(比如NOIP2012借教室,有區間修改的解法也有點修改的解法)。

主席樹的詢問(歷史區間和)

int ql, qr;//查詢區間[l,r]
int query(int o, int l, int r)//節點o代表區間[l,r]
{
    int ans = 0, mid = ((l + r) >> 1);
    if(!o) return 0;//不存在的子樹
    if(ql <= l && r <= qr) return sum[o];//區間包含返回區間值
    //都是線段樹標準操作,只不過是左右子樹多了一個記錄而已
    if(ql <= mid) ans += query(lc[o], l, mid);
    if(qr > mid) ans += query(rc[o], mid+1, r);
    return ans;
    //點操作就不用說了
}

主席樹復雜度分析

如果只按照上述做法去做的話,每次修改的時間復雜度是O(lgn)O(lgn),每次詢問的復雜度也是O(lgn)O(lgn)。

[可持久化線段樹(主席樹)]