candy
flashhu
xzyxzy
想要學lct的還是看這幾篇比較好。我這篇只是總結一些容易理解錯的或者一不小心打錯的地方。
lct(link cut tree),就是又可以link(動態加邊)又可以cut(動態刪邊)的維護一片森林的資料結構。
lct使用實鏈剖分,對每一條實鏈用splay維護(一棵splay叫一棵輔助樹),實鏈之間用虛邊相連。
lct的基本操作是access,作用是把乙個點和根之間用實鏈連線起來,其他操作都可以在access和splay的基礎上完成。
了解lct的結構。
先賀兩張圖,來自flashhu
前一張是原樹,後面的將實鏈表示成了splay的形式。
可以發現在lct的結構中,每棵splay按照深度排序,單棵splay的root不一定是這條鏈上最淺的點 (顯然),但是他的父親指標一定指向原樹中這條鏈鏈頂的父親節點。
這些是一棵正常的 lct 免不了要實現的功能。
輔助的 3 個
sonis_root
reverse
更新陣列 3 個
push_up
push_down
pre_push_down
splay 基本操作 2 個
rotate
splay
lct 基本功能
access
make_root
其他可能要用到的
find_root
split
link
cut
再其餘的功能一般都是基於上面的函式操作的。
眾所周知,access(u)的目的是將u與根放在同乙個splay中,並使u是這條實鏈上最深的點,即將 root-u 的路徑上的點全部變成實邊,u和兒子的邊全部變成虛邊。
那這時候splay 支援瞎jb亂旋 靈活地維護區間的優點就顯現出來了。
看**(盡量寫成了平易近人的樣子):
void
access
(int u)
}
自然語言複述:
把u旋轉到當前splay的root
然後斷掉他的右兒子,這樣u就是這條實鏈中最深的節點了。
在u的右兒子位置接上上乙個splay,那麼一條新的實鏈就產生了
把u變成u所在實鏈鏈頂在原樹上的父親,重複過程。
可以看出2和3步驟只用了一句ch[u][1] = v
就完成了,因為判斷乙個點u是splay的根只需要fa[u]的兩個兒子都不是u就行了,所以連上新實邊的同時也就斷掉了舊實邊。
有了access之後其他操作就都不難實現了。
用於判斷乙個點是不是他所在的 splay 的根。
bool
is_root
(int x)
因為在 lct 中,一棵 splay 中非根節點的 fa 指向他 splay 中的父親,而一棵 splay 的根 root 的 fa 指向的是 root 這棵 splay 中最淺的點的父親,不是這棵 splay 中的點。
依據這個性質可以方便地判斷乙個點是不是當前 splay 的根。
在 rotate 的時候要用到。
何時更新節點資訊是非常關鍵的。
對於 push_up(),只要兒子變了就需要 push_up()。
然後對於 push_down(),由於乙個懶標記能影響的只有打標記時他所在 splay 的子樹,後來連上的點是不能夠取影響的,所以在 access 將要連上一條實邊的時候必須保證深度淺的 splay 根上沒有標記。
與一般 splay 不同的是,在一般平衡樹上把乙個點旋轉到根之前必然是要利用二叉查詢樹的性質尋找這個節點,順便就把懶標記推下去了,但是 lct 可以隨便找乙個節點就讓你 splay 到根,那麼懶標記怎麼辦呢?
所以我們不得不暴力一點了,在想要將乙個點splay到根之前先暴力將根到他的路徑懶標記全都推掉,雖然複雜度不會變(反正要乙個乙個 rotate 上去),但是自帶大常數還是免不了了。
這是lct中splay的rotate:
void
rotate
(int u)
好像在單純的 splay 的 rotate 中並沒有加判斷呀,為什麼要加這一句呢?
可以 yy 一下,假如is_root(v)==true
,那麼w就不是這棵 splay 上的點,那麼ch[w][ws]=u
的意義是什麼呢?不就是把 u 所在的 splay 和 w 所在的 splay 合併了嗎。。。所以顯而易見 fst 。
luogu板子
#include
using
namespace std;
namespace lct
inline
intson
(int x)
inline
void
reverse
(int u)
inline
void
push_up
(int u)
inline
void
push_down
(int u)
}void
pre_push_down
(int u)
inline
void
rotate
(int u)
inline
void
splay
(int u)
inline
void
access
(int u)
inline
void
make_root
(int u)
}using
namespace lct;
int m;
intfind_root
(int x)
void
split
(int x,
int y)
intget_sum
(int x,
int y)
void
link
(int x,
int y)
void
cut(
int x,
int y)
void
change
(int x,
int y)
intmain()
return0;
}
犯過的錯誤。
push_up 的時候注意不是區間合併,父親節點也是有權值的,不能把父親給漏了~~(廢話)~~
pre_push_down 不要遞迴,手寫棧避免 mle (in 彈飛綿羊)。
閒著沒事就想想**該 push_down 了。一般功能應該都記得**該 push_down ,但是額外寫的要特別注意。
這裡總結了好多好多完全做不完:xzyxzy
大概先寫掉qtree和luogu的試煉場就可以入門了吧。。。
動態樹LCT 模板
題目描述 輸入 第一行兩個整數n和m 接下來一行中n個整數表示初始點權 接下來m行每行乙個操作如上表所示。輸出 對於每乙個連線操作,若p和q不連通,輸出yes,並新增這條邊 否則輸出no 對於每乙個刪除操作,若p和q間有邊,輸出yes,並刪除這條邊,否則輸出no 對於每乙個查詢最大及查詢和,若p和q...
詳解動態樹 LCT)
lct的功能 題意 乙個圖,有n個點,一開始圖中沒有邊。三種操作 connect u v 在點u和點v之間建一條邊。保證所有connect操作不會重複建邊。destroy u v 摧毀點u到點v之間的邊。保證所有destroy操作將摧毀的是一條存在的邊。query u v 詢問點u和點v是否聯通,是...
動態樹 LCT 錯誤總結
彙總犯過的一大堆神奇錯誤。例 node findroot node u return u 解決方法 寫完後搜尋所有 ch,檢查是否之前已pushdown 解決方法 使u uu結點懶標記意義表示u uu的兒子結點需更新,而不是u uu需要更新。懶標記下傳後未清空 例 void pushdown 例 n...