詳解動態樹 LCT)

2021-09-25 09:35:38 字數 4216 閱讀 5831

lct的功能【題意】

乙個圖,有n個點,一開始圖中沒有邊。

三種操作:

connect u v:在點u和點v之間建一條邊。保證所有connect操作不會重複建邊。

destroy u v:摧毀點u到點v之間的邊。保證所有destroy操作將摧毀的是一條存在的邊。

query u v:詢問點u和點v是否聯通,是輸出yes,否則輸出no

保證:在任何時刻,任何點到任何點最多只有一條路徑。(就是叫你用樹咯)

【輸入格式】

第一行為兩個正整數n和m,分別表示點的個數和操作的個數。

以下m行,一行表示乙個操作。

每行開頭是乙個表示指令種類的字串s(區分大小寫),之後有兩個整數u和v (1≤u, v≤n且u≠v) 分別表示兩個點的編號。

【輸出格式】

對每個query操作,輸出點u和點v是否連通:是輸出yes,否則輸出no。

【樣例輸入】

200 5

query 123 127

connect 123 127

query 123 127

destroy 127 123

query 123 127

【樣例輸出】

noyes

no【樣例輸入】

3 5connect 1 2

connect 3 1

query 2 3

destroy 1 3

query 2 3

【樣例輸出】

yesno

【資料說明 】

10%的資料滿足n≤1000, m≤20000

20%的資料滿足n≤2000, m≤40000

30%的資料滿足n≤3000, m≤60000

40%的資料滿足n≤4000, m≤80000

50%的資料滿足n≤5000, m≤100000

60%的資料滿足n≤6000, m≤120000

70%的資料滿足n≤7000, m≤140000

80%的資料滿足n≤8000, m≤160000

90%的資料滿足n≤9000, m≤180000

100%的資料滿足n≤10000, m≤200000

【**】sdoi2008

lct的結構

如果這一題沒有前兩個操作,我們就可以用樹鏈剖分來解決這一系列的操作

但是,建邊和刪邊兩個操作改變了樹的結構。因此,我們就要用新的資料結構lct來做。

lct因為是動態的,所以建立在了資料結構splay上面,如果沒有學過splay,就趕緊去學吧。

言歸正傳,lct中那棵splay是由樹中的每乙個節點的深度作為權值進行操作的。

這樣有什麼好處呢?

父親節點和兒子節點有邊相連(不然就沒有這種關係),而且深度正好相差1,而splay中的節點都有這樣乙個性質——左邊的都比我小,右邊的都比我大,所以這樣構建splay還可以找到樹中節點和節點的關係了。

看到這裡,你可能會問,如果這棵樹不是二叉樹,而是森林的話,要怎麼處理?

這裡又要用到了splay的性質——將最新訪問過的點轉到根節點。

看乙個圖:

我們會發現這顆樹中有粗邊和細邊

和樹鏈剖分一樣,我們把由粗邊連起來的點用一顆splay維護

但是,因為樹中的點都是相通的,所以在splay中,有乙個新的機制:(很重要)

假如兩個點是由粗邊相連的,比如說1和2 ,那麼

tr[2].f = 1 並且 tr[1].son[0] = 2

假如兩個點是由細邊相連的,比如說2和3,那麼

tr[3].f = 2 但是 tr[2].son[0] = 0 ,而不是=2

還有,根據題目,lct實際上是一顆無根樹,並沒有固定的根,而是通過splay的操作將最新訪問過的點變成這顆無根樹第1層的點(只有乙個,可以說是根)

lct**的實現

lct的實現建立在乙個函式access(x)上面,這個函式的功能就是將x到x所在樹(注意:指的不只是所在的splay)的根之間的路徑全部變為粗邊,順便維護一下之前的節點(會改變與這些節點細邊和粗邊)

access函式:

inline

void access (

int x )

}//access中令人發懵的splay

int tmp[n]

;inline

void splay (

int x ,

int rt )

tmp[

++s]

= i ;

while

( s )

//這個操作是將整顆splay存放在tmp中並維護splay中的翻轉標記

//為什麼會由翻轉,等一下即可見分曉

while

( tr[x]

.f!=rt &&

(tr[tr[x]

.f].son[0]

==x||tr[tr[x]

.f].son[1]

==x)

)else

}//因為是無根樹,不需要記錄root

}

在link和cut操作之前,我們還要學習乙個makeroot(x)的操作

這個操作是將x變為所在整棵樹最頂層的點(也就是根)

makeroot :

inline

void makeroot (

int x )

link操作:

inline

void link (

int x ,

int y )

cut操作 :

```cpp

inline

void cut (

int x ,

int y )

對於題目中的query操作,我們引入乙個新的操作

findroot:

int find_root (

int x )

對了reverse和rotate操作還要給出

inline

void reverse (

int x )

inline

void rotate (

int x ,

int w )

完整的**:

#include

using

namespace std ;

inline

intread()

const

int n =

1e4+

100;

struct node

} tr[n]

;inline

void reverse (

int x )

inline

void rotate (

int x ,

int w )

int tmp[n]

;inline

void splay (

int x ,

int rt )

tmp[

++s]

= i ;

while

( s )

while

( tr[x]

.f!=rt &&

(tr[tr[x]

.f].son[0]

==x||tr[tr[x]

.f].son[1]

==x)

)else}}

inline

void access (

int x )

}inline

void makeroot (

int x )

inline

void link (

int x ,

int y )

inline

void cut (

int x ,

int y )

int find_root (

int x )

char s[11]

;int

main()

}return0;

}

動態樹LCT 模板

題目描述 輸入 第一行兩個整數n和m 接下來一行中n個整數表示初始點權 接下來m行每行乙個操作如上表所示。輸出 對於每乙個連線操作,若p和q不連通,輸出yes,並新增這條邊 否則輸出no 對於每乙個刪除操作,若p和q間有邊,輸出yes,並刪除這條邊,否則輸出no 對於每乙個查詢最大及查詢和,若p和q...

動態樹 LCT 錯誤總結

彙總犯過的一大堆神奇錯誤。例 node findroot node u return u 解決方法 寫完後搜尋所有 ch,檢查是否之前已pushdown 解決方法 使u uu結點懶標記意義表示u uu的兒子結點需更新,而不是u uu需要更新。懶標記下傳後未清空 例 void pushdown 例 n...

動態樹(LCT)學習筆記

candy flashhu xzyxzy 想要學lct的還是看這幾篇比較好。我這篇只是總結一些容易理解錯的或者一不小心打錯的地方。lct link cut tree 就是又可以link 動態加邊 又可以cut 動態刪邊 的維護一片森林的資料結構。lct使用實鏈剖分,對每一條實鏈用splay維護 一棵...