可持久化線段樹

2022-10-05 19:18:14 字數 3354 閱讀 6982

可持久化資料結構總是可以保留每乙個歷史版本,並且支援操作的不可變特性。

對於這個說法,我表示非常贊同,因為可持久化的標誌就是在修改的過程中仍然可以保持原子樹的性質,對於直接全域性更改,倒不如把原來的一部分留下,用新的空間來記錄當前更改後的值,只改我們需要得到那一部分,因此我們開始研究可持久化的資料結構,幫助我們解決涉及可持久化的一類題目。

在本篇中我們著重講可持久化線段樹這一知識點

可持久化陣列

題目描述:如題,你需要維護這樣的乙個長度為 \(n\) 的陣列,支援如下幾種操作

在某個歷史版本上修改某乙個位置上的值

訪問某個歷史版本上的某一位置的值

此外,每進行一次操作(對於操作2,即為生成乙個與查詢版本完全一樣的版本,不作任何改動),就會生成乙個新的版本。版本編號即為當前操作的編號(從1開始編號,版本0表示初始狀態陣列)

題目幾乎已經把可持久化拍到了臉上,我們考慮對於這一道題來說,陣列的可持久化,我們肯定是不好維護的

我們考慮線段樹,先把這個陣列按照線段樹的方式建出來,但是這個題不要求我們區間求和,所以我們不需要\(\mathrm\) 操作,先在葉子節點建出樹來即可

對於樣例的話 \(59~46~14~87~41\)

大概就是長成這樣

這時候我們開始有了單點修改的操作

0 1 1 14

即把初始狀態的陣列進行更改,把 \(a_1\) 改成 \(14\)

這時候我們就要想,現在我們要保持原陣列不變,那我們不妨再開乙個節點,然後讓這個新的子樹的 [1,2]區間的左兒子連向這個節點,這樣的話這個圖長成這樣,這個屬實是難畫啊,我們就簡潔畫一下了

紅色的節點是我們的新建節點,橙色的節點是之前的節點,我們發現,此時我們如果需要重新建一棵樹的話太浪費了,有很多樹的節點都沒有被修改卻白白浪費電,所以我們考慮像上面的那個圖一樣了,我們發現沒改乙個點,只需要改從根節點到它的每個祖先節點,這些邊組成了一條鏈,因此每次我們只需要新建 \(\log n\) 條邊,我們的空間範圍可能就會因此大一些,因為我們每次都存下了每一條邊。

而且還有乙個關鍵的資訊,我們每次都會是要建新的根節點的,我們可以根據根節點來確定是第幾次修改的

這道題的完整**如下:

首先這裡是要支援修改的,所以我們要寫動態開點線段樹,對於每個點,我們肯定是有一側要建新點的,直接在從根節點往下傳遞時處理即可

/*

blackpink is the revolution

light up the sky

blackpink in your area

*/int n, m, t, ans, cnt, opt, x, k, pre;

int a[n], rt[n];

struct tree tr[n << 5];

inline void build(int &p, int l, int r)

inline void update(int &p, int pre, int l, int r, int x, int k)

inline int query(int p, int l, int r, int x)

int main()

else

}return 0;

}//write:revolutionbp

可持久化線段樹(主席樹)
題目要求:給定乙個陣列 \(a\),m次操作查詢區間第 \(k\) 小值

\(1\le n,m\le 2\times 10^5\)

\(1\le a_i \le 10^9\)

我們考慮這個題的本質就是要求每次查詢在 \(\log n\) 複雜度內找到靜態區間的第 \(k\) 小值

那麼就是我們要用 \(n\log n\) 複雜度預處理這個陣列,那麼這題就明顯多了,線段樹一定是必要的

但是這時候我們還要去想,我們用什麼方式來處理這個陣列

我們不妨用值域線段樹來維護

首先我們要對這個序列進行一下離散化

對於 \(\forall i\in [1,n]\) 前 \(i\) 個點建一棵樹,樹上的每個節點 \([l,r]\) 就是存離散化以後值在 \([l,r]\) 之間的有多少個。那麼這題就很好做了。我們對於區間 \(a_l\) 到 \(a_r\) 的查詢時,分別取出 \(tree_\) 和 \(tree_r\) 然後讓兩者的對應節點相減,每個點就代表了在這一段中每乙個點在各個樹上區間的出現次數,我們要查詢第 \(k\) 小,如果對於當前節點的左兒子值為 \(a(a < k)\),那麼很顯然,我們就要在右兒子中找,此時我找的就不是第 \(k\) 小了,因為在右子樹這棵子樹中,應該是第 \(k - a\) 小

那麼我們很好奇,這個演算法和可持久化有什麼關係呢?我們想,如果我們直接這麼建樹的空間複雜度是多少?

答案:\(2*n^2\)

這肯定是吃不消的,所以我們考慮能不能優化空間,答案是可以的,因為我們要是想要記錄前 \(i\) 棵子樹的值的話,第 \(i-1\) 棵子樹必然也是能用上的,我們不妨就讓我們當前節點繼承上第 \(i-1\) 棵子樹的值,然後再加上 \(a_i\) 這個點的貢獻,對於樣例,我們把樹建出來,是不是就長成下面這樣(空節點為 \(0\)),這樣的話,我們建樹的複雜度就只有 \(n \log n\)。

我們下面放出**:

首先是離散化,離散化的話看起來很麻煩,其實我們可以理解成乙個對映(如果你覺得很簡單,說明你比我強太多了o(╥﹏╥)o)

對於每個點,我們找一下它的值的排名,所以cpy出來乙個新陣列 \(b\),然後讓 \(b\) 進行排序並去重

/*

blackpink is the revolution

light up the sky

blackpink in your area

*/int n, m, t, ans, l, r, k, cnt;

int a[n], b[n], rt[n];

struct treetr[n << 5];

inline void update(int &p, int pre, int l, int r, int k)

inline int query(int l, int r, int l, int r, int k)

int main()

while (t--)

return 0;

}//write:revolutionbp

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

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

可持久化線段樹

可持久化線段樹,意思是可以查詢歷史記錄的線段樹。又叫主席樹。我們可以通過記錄不同的根節點,並在每乙個更新到的節點處新建必要的節點。詢問不同版本的主席樹,只需要進入不同的根節點即可。例題 給定n,m,輸入n個數組成的數列,有m個詢問,每次詢問l,r這個區間中,第k小的數的值。分析 這個題可以巧妙運用主...

可持久化線段樹

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