LCT總結筆記

LCT的定義和性質

LCT在樹鏈剖分的基礎上,還可以滋磁動態連/刪邊等操作。

LCT維護的是splay組成的森林,有以下性質:
1.每個splay中序遍歷得到的節點序列深度是遞增的,序列深度之間兩兩相差1。
2.每個節點被包含且僅被包含於一個splay中。
3.若父親和孩子在同一個splay中,則它們連的是實邊,反之是虛邊。

性質1推論:原樹中一個節點向孩子最多隻能連一條實邊。
證明:原樹中,一個節點的兩個孩子不可能在同一棵splay中,否則會無法使節點序列深度單調遞增。也就是說,原樹中每個節點最多隻能向孩子連一條實邊,向其他孩子連的都是虛邊。

性質3推論1:若兩個節點在同一個splay中,則它們之間有一條實邊路徑相連。
證明:顯然。

性質3推論2:在splay中,一個節點和它的右孩子連接的是實邊。
證明:因爲它的左孩子比它深度小1,是它的父親。它的右孩子比它深度大1,是它的孩子。又由性質3,可知這個節點和它的右孩子連的是實邊。

在LCT中,無法從虛邊的父親訪問孩子,只能從虛邊的孩子訪問父親。也就是說,虛邊是“認父不認子”的。

access操作

access操作:打通某個節點到根節點的實鏈,即把這個節點到根節點的路徑上的邊都改爲實邊,然後把連了兩條實邊的節點的另一條實邊改爲虛邊。
嫖來YangZhe論文裏的圖:以下面這棵樹爲例。假設原樹中一開始實邊、虛邊這樣區分:

假設現在想access(N),即把N到A的實鏈路徑打通。
首先需要讓N所在splay中深度最淺的節點打通它與父親的實邊。也就是說,需要讓L與I變成實邊。
L與I變成實邊後,I原來連的實邊就要變成虛邊,否則一個節點就會向孩子連兩條實邊,這是不符合性質1推論的。原樹變成這樣(注意節點K,I,L附近邊的變化):
在這裏插入圖片描述
這樣的操作成功地讓L和I的實邊打通了!同理,我們繼續讓H和I之間連實邊。爲了讓H滿足只向孩子連一條實邊的性質,要讓H本來連向孩子J的實邊變成虛邊。
接着,同理把A和C的實邊打通,把A和B的實邊變成虛邊,就打通了從N到A的路徑。

(圖中讓N與O之間變成輕邊只是爲了讓程序易於實現,接下來會說實現方式)
現在總結打通路徑的過程:
對於每個節點,想要打通它與根節點之間的路徑,需要讓它到根節點路徑上原來是虛邊的邊變成實邊(廢話)。然後讓連了兩條實邊的節點的另一條實邊變成虛邊。
因此,每次找到當前splay中深度最淺的節點,此時它與父親的邊一定是虛邊。否則它與父親在同一棵splay中,這樣它就不是splay中深度最淺的節點,矛盾。
把它與父親之間的邊改爲實邊。因爲它的深度肯定比父親的深度大,所以直接把父親節點的右孩子設爲它就好了(這樣也順便把父親原來連向孩子的實邊斷掉了)。
代碼可以表示成這樣:

void access(int x) {
	for(int y=0;x;y=x,x=w[x].fa) {	//y表示下面的splay的深度最淺的節點。x表示y的父親,x與y連的是虛邊。
		splay(x);
		w[x].ch[1]=y;
		pushup(x);	//維護x的信息
	}
}

發現這樣access後,access的節點原來連向孩子的實邊變成了虛邊。於是它變成了它所在splay中深度最大的節點。

makeroot操作

makeroot是“將原樹的根設置爲某個節點”的操作。makeroot會使要設爲根的節點變爲深度最小的節點,而單純的access會讓這個節點變爲它所在splay中深度最大的節點。
考慮把xx設爲根會對它的splay產生什麼影響。LCT的性質1指出:每個splay中序遍歷得到的節點序列深度是遞增的,序列深度之間兩兩相差1。所以access(x)後xx所在splay中序遍歷的深度應爲1,2,...,depth(x)1,2,...,depth(x)
xx換爲根後,yy一下可以發現中序遍歷的深度將會變爲depth(x),depth(x)1,...,1depth(x),depth(x)-1,...,1。然後可以通過互換所有節點左右孩子來維護splay性質,通過lazytag實現。

inline void pushrev(int x) {
	swap(lc(x),rc(x));
	w[x].tag^=1;
}
void makeroot(int x) {
	access(x);
	splay(x);
	pushrev(x);
}

findroot操作

findroot是找到xx所在的原樹中的根,也就是找到深度最淺的節點。
可以通過打通xx到根路徑,把xx旋轉到根,然後不斷地找左孩子實現。
當然,不斷找左孩子的時候要注意pushdown。

int findroot(R x){
    access(x); splay(x);
    while(lc(x)) {
    	pushdown(x);
		x=lc(x);
    }
    splay(x);
    return x;
}

link操作

link操作是在x,yx,y之間連一條輕邊。如果x,yx,y已經在同一個聯通塊中則不用連邊。判斷x,yx,y在同一個聯通塊中的方式是先makeroot(x),然後findroot(y)==x說明在同一個聯通塊中。

inline void link(int x,int y) {
	makeroot(x);
	if(findroot(y)!=x) {
		w[x].fa=y;
	}
}

cut操作

cut操作是刪除x,yx,y之間的邊(無論虛實)。如果保證x,yx,y在同一個聯通塊中x,yx,y之間有一條邊相連,那麼makeroot(x),access(y)後把xxyy之間的邊斷去即可。
判斷x,yx,y在同一個聯通塊中的方法是makeroot(x)然後判斷findroot(y)==x。而現在仍需判斷x,yx,y之間是否有一條邊。
注意到一通makeroot和findroot使我們把xx旋轉到了它所在splay的根,且xx是splay中深度最小的節點。如果x,yx,y之間有一條邊,那麼yy深度一定比xx大1。
因此只需要判斷yy父親爲xxyy左孩子爲空即可。(或者可以判斷xx右孩子爲yyyy左孩子爲空)

void cut(int x,int y) {
	makeroot(x);
	if(findroot(y)==x && w[y].father==x && !w[y].ch[0]) {
		w[y].father=w[x].ch[1]=0;
		pushup(x);
	}
}

split操作

split操作是拉出一條xxyy的splay。這個操作就比較顯然了。

void split(int x,int y) {
    makeroot(x);
    access(y);
    splay(y);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章