前前言
話說前面部分大概是我一年前搞的吧。
最近又複習了一哈LCT,那就補補坑吧。
注意,爲了區分,從我寫spaly開始都是現在寫的。
前言
首先,LCT是什麼呢?
我們看看百度百科——
LCT即自動細胞學檢測系統,又稱液基細胞學檢測系統。是宮頸篩查的一種方法。
疏鬆結締組織(loose connective tissue)又稱蜂窩組織(areolar tissue),其特點是細胞種類較多,纖維較少,排列稀疏。疏鬆結締組織在體內廣泛分佈,位於器官之間、組織之間以至細胞之間,起連接、支持、營養、防禦、保護和創傷修復等功能。
好吧,不是這個。
LCT即爲link-cut-tree
是一種解決動態樹的有效利器。
動態樹是一類問題,常見的有維護樹的連通性,求樹上路徑的極值等。
然後LCT即爲一種用Splay來維護樹鏈剖分的算法,簡單理解爲“支持刪邊、加邊的動態樹鏈剖分”
前置技能:
樹鏈剖分https://blog.csdn.net/HiChocolate/article/details/77170675(不會的可以看看我寫的)
Splay https://blog.csdn.net/qq_30974369/article/details/77587168(不會的可以看看不是我寫的)
重點是樹鏈剖分的全部內容加上splay的一些基本操作(如標記下傳、區間翻轉《排序機械臂》)
一些定義
既然百度百科沒有很好的定義來學習,那我就來大致講講。(實際上是我懶得打定義)
LCT的本質也是樹鏈剖分,它的樹也是用很多的偏愛路徑和非偏愛路徑來連接起來的。
這些爲了方便理解,不妨也用樹鏈剖分的定義來定義。
1、重邊連起來會組成重鏈,重鏈之間沒有公共點。
我們首先跑一遍算出dfn序之後,我們就可以找到很多很多條重鏈。
2、那麼對於一顆樹,其中一條重鏈的點,都在一顆splay中,關鍵字是深度。
3、樹鏈剖分的重鏈是一成不變的,但是LCT的重鏈是可以隨意改變的。這也註定了LCT的重鏈需要用到splay來維護。
4、重鏈與重鏈之間的邊在splay中用虛邊來表示。
5、一條重鏈的虛邊會存在它的splay的根上,指向這條重鏈的頂點的father所在的重鏈的splay的根。
就是這麼多啦。
其實這樣子很難理解4與5的定義什麼意思,那麼我們畫畫圖。
上面加粗的邊即爲重邊,然後連成重鏈。
然後,我們把每一條重鏈都給弄出來,對它建一顆splay,那麼就變成——
那麼對於上面的這個圖,三角形就代表一個splay,每個splay對應着LCT上的每個鏈。
那麼有些鏈是用一條輕邊連起來的,那麼在splay上就用一個虛邊連接。
理解定義5,就會發現在這張圖上,這些有向邊可以表示爲虛邊。
然後就大致介紹完LCT的基本定義與構造了。
一些乾貨
由於我們的這個LCT並不需要把它建出來,只需要維護spaly即可。
因此,我們節點維護下面的一些基本值:
- fa表示當前節點spaly上的父親
- pf表示當前節點在LCT上虛邊連接的點
- son[1/0]在spaly上的兒子
關於spaly
其實spaly在LCT中只需要利用其旋轉,就可以基本完成一切操作(廢話)
那麼在旋轉過程中,需要改進的只有一個向虛邊傳送。
代碼
void rotate(int x)
{
int y=t[x].fa,k=get(x);
if(t[y].fa) t[t[y].fa].son[get(y)]=x;
if(t[x].son[!k]) t[t[x].son[!k]].fa=y;
t[y].son[k]=t[x].son[!k],t[x].fa=t[y].fa,t[y].fa=x,t[x].son[!k]=y;
update(y),update(x);
t[x].pf=t[y].pf;//就在這裏!!!
}
核心操作access
我們定義一個操作表示把當前x到根的所有邊都變成重邊,然後把這條鏈以外的所以邊變成輕邊。
具體操作看圖:
(盜盜圖)
應該很好理解。
代碼:
void access(int x)
{
for (int y=0;x;update(x),y=x,x=t[x].pf)//一直跳重鏈
{
splay(x,0);
t[t[x].son[1]].fa=0;//斷掉原來的重鏈
t[t[x].son[1]].pf=x;
t[x].son[1]=y;//把下面的spaly接到上面
t[y].fa=x;
t[y].pf=0;
}
}
基本操作1 makeroot
我們定義一個操作表示把x變成LCT的根。
操作簡單,先access一下,那麼我們就可以得到一顆spaly。
然鵝這顆spaly最右邊的點是x,我們要把他變成最左邊。
於是把它旋轉到根,翻轉一下即可(翻轉就是排序機械臂中的翻轉)
代碼:
void make(int x)
{
swap(t[x].son[0],t[x].son[1]);
t[x].tag^=1;
}
void makeroot(int x)
{
access(x),splay(x,0);
make(x);
}
基本操作2 findroot
我們定義一個操作表示尋找x所在樹的root。
這個操作有什麼用呢?
你可以用這個代替並查集,只是時間多了些
用醋很多,就不一一介紹了。
如何實現?
access(x)一下,然後我們發現x和原本的根在同一spaly上。
然後詢問spaly上最左邊的點即可。
int findroot(int x)
{
access(x),splay(x,0);
for (;t[x].son[0];x=t[x].son[0]);
return x;
}
基本操作3 link
這個就很帥氣了。
我們定義一個操作表示連接x和y這條邊。
方法很簡單,我們先makeroot(x),然後把x連接到y即可。
void link(int x,int y)
{
makeroot(x);
if(findroot(y)!=x) t[x].pf=y;
}
基本操作4 cut
我們定義一個操作表示刪掉x和y這條邊。
我們考慮makeroot(x),然後access(y)。
如果x和y之間有連邊,那麼y必定是x的兒子。
斷掉即可。
void cut(int x,int y)
{
makeroot(x),access(y),splay(y,0);
if(t[x].fa==y) t[y].son[get(x)]=t[x].fa=t[x].pf=0;
}
應用
大致就是上面的這些操作。
例題?洛谷上很多。
當然,https://blog.csdn.net/HiChocolate/article/details/101380259
這也是一個模板。