可持久化線段樹

2022-02-06 05:09:05 字數 1656 閱讀 3078

可持久化線段樹,意思是可以查詢歷史記錄的線段樹。又叫主席樹。我們可以通過記錄不同的根節點,並在每乙個更新到的節點處新建必要的節點。詢問不同版本的主席樹,只需要進入不同的根節點即可。

例題:給定n,m,輸入n個數組成的數列,有m個詢問,每次詢問l,r這個區間中,第k小的數的值。

分析:這個題可以巧妙運用主席樹來解題。

首先對數列進行離散化。

我們令線段樹的每乙個節點,都代表排名為[i,j]區間內數字出現的次數。

假如我們每加入乙個數,就新建一棵線段樹,這一刻線段樹大部分資訊與前一棵一致,只是新加入的乙個數相關的位置的資訊發生了改變。相當於第i棵線段樹,都維護的是[1,i]前i個數的資訊。

發現對於區間查詢[l,r]來說,對於任意乙個排名位置上的數,它在這個區間裡出現的次數,就是第r棵線段樹上出現的次數減去第l-1棵線段樹上出現的次數。同理,我們也可以利用這個字首和思想算出來當詢問[l,r]時,某個排名區間[i,j]裡,所有的數出現的總次數。(只需要讓兩個版本的線段樹中的對應區間的sum值相減即可。)

這樣我們將數列從1到n掃一遍,每一次都將a[i]對已經的離散化的值(即排名)的位置加上1,(常規線段樹操作)但是每次新建乙個版本的線段樹會使時空複雜度**。

然而我們發現,相鄰兩個版本的線段樹之間記錄的資訊基本相差無幾,重新複製一遍實在是浪費。

所以採用主席樹操作,就是僅改變logn個點的情況下,新建一棵線段樹。

具體的操作是:

1.常規add操作中,每新到乙個舊節點,就新建乙個同樣的節點,lson,rson都不變,只是這個區間內維護的sum(數字出現的次數)要比之前多乙個。

2.之後,再根據待加入點與mid的關係,更新新節點左二子或者右兒子。

形象的理解一下,就是在原始線段樹上,從根節點開始,沿著加入這個數的路徑上新建了一條線。好像貼了一層皮。

查詢操作是:

1.同時處理訪問兩個版本的線段樹,直接處理sum的差值x。

2.如果這個差值要大於等於k,則第k大的數一定在這個節點的左二子維護的位置裡;反之,詢問右兒子中第k-x大的數即可。

詳見**:

#includeusing

namespace

std;

const

int n=2e5+10

;int

cnt;

intn,m;

intid,root[n];

inta[n],b[n];

int li(int

x)struct

nodet[

18*n];

void pushup(int

x)void build(int x,int l,int

r)

int mid=(l+r)>>1;ls(x)=++id;rs(x)=++id;

build(ls(x),l,mid);

build(rs(x),mid+1

,r);

}int add(int x,int l,int r,int

to)

return

now;

}int query(int u,int v,int l,int r,int

k)int

main()

intl,r,z;

for(int i=1;i<=m;i++)

return0;

}

可持久化線段樹總結(可持久化線段樹,線段樹)

最近正在學習一種資料結構 可持久化線段樹。看了網上的許多部落格,弄了幾道模板題,思路有點亂了,所以還是來總結整理下吧。你需要維護這樣的乙個長度為 n 的陣列,支援如下幾種操作 在某個歷史版本上修改某乙個位置上的值 訪問某個歷史版本上的某一位置的值 此外,每進行一次操作 對於操作2,即為生成乙個完全一...

可持久化線段樹

以p3919 模板 可持久化陣列 可持久化線段樹 平衡樹 為例。知識點 1.練習可持久化線段樹 2.線段樹維護數列。線段樹維護數列單點查詢僅需o logn 3.記得return root 4.記得設定左右兒子 5.有時需注意cnt的初始大小 include using namespace std i...

可持久化線段樹

可持久化資料結構總是可以保留每乙個歷史版本,並且支援操作的不可變特性。對於這個說法,我表示非常贊同,因為可持久化的標誌就是在修改的過程中仍然可以保持原子樹的性質,對於直接全域性更改,倒不如把原來的一部分留下,用新的空間來記錄當前更改後的值,只改我們需要得到那一部分,因此我們開始研究可持久化的資料結構...