1. 程式人生 > >關於動態樹和LCT的一些學習感受(持續更新)

關於動態樹和LCT的一些學習感受(持續更新)

實現 劃分 沒有 學習 clas 找到 acf 樹操作 全部

什麽是動態樹?

動態樹(Dynamic Tree)問題是指在樹上動態維護相關信息的問題。

一般的動態樹問題中,會要求我們維護一個由若幹棵子結點無序的有根樹組成的森林。並且要求這個數據結構支持對樹的分割(刪邊),合並(加邊),對某個點到它的根的路徑的某些操作(路徑操作)。有時,動態樹問題還會涉及對某個點的子樹進行的某些操作(子樹操作),而涉及子樹操作問題的動態樹問題更加復雜,需要用到更加復雜的數據結構。然而蒟蒻並不會做這種高級的題目,所以就講講簡單的幾種操作。

關於LCT的一些筆記

LCT(Link-Cut Trees)是解決這類動態樹問題的一種數據結構。這個數據結構可以在均攤O(log n)的時間內實現上述動態樹問題。

LCT 的詳細介紹,大家可以參考楊哲的《QTREE 解法的一些研究》,這裏貼文庫鏈接:https://wenku.baidu.com/view/75906f160b4e767f5acfcedb.html

簡單來說,LCT 與樹鏈剖分一樣,會對節點的兒子進行劃分。樹鏈剖分中根據節點子樹大小來劃分輕重兒子,並用數據結構維護重鏈;而 LCT 則會將兒子劃分為虛、實兩種兒子,相應的邊稱為虛邊或實邊,且任意時刻一個節點最多只會有一個實兒子(可能沒有)。由於樹的形態會改變,因此 LCT 不是嚴格的劃分虛實兒子,而是動態地改變,它同樣使用了數據結構來維護實鏈(連續的實邊),並且用的是更加靈活的 Splay.

關於LCT的一些基本定義:

1.深度:深度越大(小),到根節點路徑的距離越長(短)。

2.實邊:一個非葉節點,向它的兒子中的一個連一條特殊的邊,稱為實邊;該非葉節點向它的其他兒子所引的邊均為虛邊。註意,對於某些非葉節點,它與它兒子們所連的邊可能全部是虛邊。

3.實路徑(鏈):由若幹條實邊首尾相連而成的、不可伸長的路徑稱為實路徑。由實邊的定義可知,實路徑上的點深度各不相同且為連續自然數。其中,不可伸長指的是,設一個實路徑上最淺節點為 u,則 u 和 u 的父節點之間的邊不能是實邊;設一個實路徑最深節點為 v,則 v和它兒子們所連的邊全是虛邊。

4.實路徑的父親:可以得出各條實路徑之間是由虛邊連接,對於一條實路徑,我們定義它的實路徑父親(Path_Parent)為它的最淺的節點的父親,如下圖:

技術分享圖片

其中紅色路徑為實路徑,那麽對於2-5-6這條實路徑,它的實路徑的父親就為2的父親,即為1。

關於與LCT的一些基本操作

用 Splay維護實路徑:由於每條實路徑是由首尾相連的實邊構成的,因此實路徑上任意兩個點都是祖先與子孫的關系。換句話說,如果用深度作為關鍵字給結點排序,那麽我們將得到一個唯一的有序結點序列。

基於這個思想,我們將這個結點序列用平衡樹維護,平衡樹中每個結點的左子樹中結點在實路徑中的深度都小於該結點,右子樹中的都大於該結點,因此平衡樹的最左結點對應該路徑的頭部,最右結點對應該路徑的尾部。

一些基本操作(註意:在操作過程中並不需要考慮Splay的形態,而是只用考慮當前樹的形態):

1.Access(x):以x為起點一直到根節點構造出一條鏈。

這是針對某個結點 x 的操作, 該操作將 x 到根結點的路徑上的所有邊都變為實邊, 當然,為了保持實邊、虛邊劃分的性質,一部分原來的實邊也要相應變為虛邊。註意該操作會將 x下方的實邊變為虛邊。該操作的步驟如下:

(1):如果結點 x 不是其所在實路徑的尾部, 即 x有子結點與之用實邊相連, 那麽需要 “斷開”這條邊(斷開並不是將這條邊刪除,而只是將其轉變為虛邊)。方法是首先將結點 x用 Splay操作旋轉到所在平衡樹的根結點,然後 x 肯定有右子樹,故將 x 與 x的右子樹分離,同時將 x 的右子樹的 Path_Parent 設置為 x。

(2):如果結點 x所在的平衡樹包含根結點,那麽該過程結束;否則,轉步驟(3);

(3):設 y 為 x 所在平衡樹的 Path_Parent。將 y 用 Splay 操作旋轉到其所屬平衡樹的根結點,並且用 x 所在的平衡樹替換 y的右子樹,這樣就實現了實路徑的向上延伸。當然到這裏還沒有結束,我們需要分離原來 y 的右子樹,y原來的右孩子記為 P。此時 P 的 Path_Parent就為 y了,然後繼續轉步驟(2)。

實際實現時, 我們並不需要顯示維護出 Path_Parent, 我們可以稍微更改 Splay中的實現,即我們考慮 Splay中根節點的 fa,我們並不需要將其置為 0,而是將其置為 Path_Parent,這樣每個點的父親,要麽是實路徑上的點,要麽是它所處實路徑的 Path_Parent,特別地,這棵樹的根節點的 Path_Parent 為 0。這樣的話一個點,它的 fa的左右兒子可能都不為它,因此判斷根節點的條件也需要稍微修改一下。下面給出具體實現過程的圖示(紅邊表示實邊,帕金森晚期患者):

技術分享圖片

因為維護的關鍵字為深度,所以刪除前Splay上肯定有x的子樹那一段,所以將x通過Splay旋轉到根節點處,直接將x的右子樹與它分離,並把右子樹的Path_Parent改為x(實際操作並不需要)

技術分享圖片

技術分享圖片

需要註意的是,將y的右子樹換成x所在平衡樹後,原右子樹的Path_Parent就變成了y。

2.Findroot(x):找到節點x所在樹的根節點。

有了 Access(x)操作,尋找樹根的操作就So easy了。我們只要對結點 x 執行Access(x),便使得 x 與要找的根結點在同一棵平衡樹中了。然後,要找的根結點一定是實路徑的頭部(因為深度最小),即平衡樹中的最左結點。

圖示:

技術分享圖片

3.Makeroot(x):使節點x成為所在樹的根節點。

首先我們觀察下面這個圖:

技術分享圖片

對於一棵有根樹而言,它的父子關系是確定了的,圖中由父親指向兒子。

那麽如果我們把2作為整棵樹的根呢?那麽它就會長這樣:

技術分享圖片

如果還沒發現規律的話,我們再以5為整棵樹的根:

技術分享圖片

沒錯,以誰為根,就是將該點到根節點路徑父子關系取反,所以該操作等價於將從結點 x到當前根結點的路徑上的所有樹邊的方向取反。首先執行 Access(x),我們便已經將該路徑取出了。由於路徑是用平衡樹維護,所以需要執行的是平衡樹的區間翻轉操作,我們可以借鑒線段樹的打標記(懶操作)思想,給平衡樹結點也用打標記的方式來完成反序操作。這裏由於是對整棵平衡樹反序,所以標記應該打在平衡樹的根結點處。打標記後註意在其他操作的地方(例如旋轉後)及時下傳以及更新。

4.Link(x,y):使x成為y的子節點,也就是連一條(x,y)的邊

這只需要執行一次 MakeRoot(x)操作,隨後將x的fa置為y即可。

圖示:

技術分享圖片

5.Cut(x,y):刪除樹中(x,y)這條邊

該操作執行時,首先將 x 置為有根樹的根結點,從而保證 y一定是 x 的子結點。再對 y執行 Access 操作,我們便將 x 和 y 合並到同一平衡樹中了。此時對 y 執行 Splay 操作使其成為所在平衡樹的根結點,同時分離 y和 y 的左子樹,便完成了邊的刪除操作。(此時若樹中存在(x,y)這條邊,則 y的左子樹一定只有 x 這一個節點)。

圖示:

技術分享圖片

6.Select(x,y):取出(x,y)在樹中的路徑從而進行區間操作。

執行一次 MakeRoot(x)將 x 置為其所在樹的根,再執行一次 Access(y)操作,就將 x 與 y以及它們路徑上的所有點整合至同一棵平衡樹中了。 此時我們在平衡樹的根節點上打上標記或者直接查詢信息即可。

圖示:

技術分享圖片

然後就是關於LCT的一些奇奇怪怪的例題,這裏給一道:

BZOJ2049:洞穴勘探,題目及題解傳送門:(啦啦啦待更)

關於動態樹和LCT的一些學習感受(持續更新)