link cut tree簡稱lct,是維護動態樹方式的一種,是乙個可以對樹進行新增鏈或子樹,刪除鏈或子樹等等,可以支援對樹的結構進行修改的演算法。
樹鏈剖分只能維護靜態樹,就是只能對樹上的點的值進行修改的演算法,一般還是用線段樹來維護的。
所以lct就厲害了,首先是維護方式不同,其次它是用splay來維護的。樹鏈剖分可以幹的,lct都可以幹,但lct可以幹的,樹鏈剖分有些並不能幹。而且lct短啊!
學習lct,必須要有splay的基礎。如果還不會splay,請看splay學習小記
首先要知道一些概念。
現在有一棵樹,它需要維護,我們稱其為原樹。
splay本身其實乙個樹形結構,我們稱其為輔助樹。
我們用輔助樹來維護原樹,注意這是兩顆不同的樹。
類似樹鏈剖分維護重鏈,lct需要維護的是偏愛路徑
剛開始我學習lct看網上的資料時,一直看不懂,偏愛路徑到底是什麼。經歷了好長的一番摸索。
我們首先來講什麼是偏愛兒子。偏愛兒子就是你最後訪問到的節點。比如說圖1access之前的樹(先不要管access是什麼),我們先不斷的加點進去,比如說加入了乙個點o,那麼這個點就是偏愛節點。簡單的來說最後訪問到的點就是當前你要對這個點進行操作,那麼這個點就是偏愛節點。(如果還不是很清楚,講到access就清楚了)。
偏愛節點到根的路徑就是偏愛路徑。
偏愛路徑上的邊叫做偏愛邊。
節點x上第一條邊不為偏愛邊的連線點叫做path parent。比如說圖1中access之前的樹,l的path parent就是i。用pfa陣列來表示。
splay中每個x有兩個子節點,t[x][0]和t[x][1]。
序列中的splay是左兒子的序號比x小,右兒子的序號大於x。所以在樹上的情況類似:左兒子在樹中的深度小於x,右兒子在樹中的深度大於x。有deep[x]表示節點的深度,在樹上splay維護的條件就是deep[t[x][0]]≤deep[x]≤deep[t[x][1]]。
將根節點到x的路徑變為偏愛路徑
這個過程名叫access,是lct中的核心。如圖1,access(n)之後,a到n的路徑就變成了偏愛路徑。
但是,為什麼其他路徑上還保留著偏愛邊呢,不是含有最後訪問的節點的路徑才是偏愛路徑嗎?
其實lct為了方便維護,更新偏愛路徑並不會把原來所有的偏愛路徑刪去,只會修改原來偏愛路徑最與path parent相連線的那一條邊而已。並不是只有最後乙個節點的那一條路徑有偏愛邊,只是那一條到的路徑全都是偏愛邊及偏愛路徑(仔細地研究圖一的後面那張圖,因為輔助樹只要deep[t[x][0]]≤deep[x]≤deep[t[x][1]]就可以了,所以與path parent相連線的頂點不一定是深度最小的節點)。
一般只有對某個節點x進行操作就需要打access(x)。
void access(int x)lca=y;
}
這裡的lca有什麼用後面再說。
access的思路就是:跟著pfa一直往上跳,一直跳到pfa為0且這個節點是當前輔助樹的根節點就停止。其實就是將偏愛路徑一直向上延伸,知道無法延伸為止。所以建立根節點到x節點的偏愛路徑結束之後,根節點並不一定是原來的根節點,就是根節點可能會變換。假設當前的root=u,access(x)之後,root可能變為了v,這一點很重要,會關係到後面操作的理解。
access需要好好的理解一番……
我們來分析一下,splay(x,0)就是把x旋轉為它所在的這顆樹上的根節點(這就體現了把原來偏愛邊保留的優勢了)。因為有splay,所以這顆樹的root會變。
然後就要把當前x與右兒子的連邊斷開,因為access(x)是讓1到x的路徑為偏愛路徑,x的子樹上的邊都不在偏愛路徑中。因為t[x][1]的深度比x大,所以把連邊刪掉。pfa[t[x][1]]=x,因為現在x現在與是t[x][1]斷開了連線,那麼pfa[t[x][1]]=x,x與t[x][1]之間的連邊成為t[x][1]上第一條不為偏愛邊的邊。
然後把x與y進行連邊,這個y初始為0,看看最後一行 y=x,x=pfa[x],所以之後y會變為x,x會變成pfa[x],那麼這裡的連邊就是把x和pfa[x]進行連邊。
然後把pfa[y]清零,可見這有當x為這顆偏愛邊組成的子樹(這顆子樹的偏愛邊可以為0)的根節點時才會有path parent。如圖1的後面那張圖,只有細邊下面的點才會與path parent,其他點(包括根節點)的path parent都為0。
然後在更新x的值就好了。
換根操作
void makeroot(int x)
把x變為當前這棵樹的根節點(對於lct來說是可以維護森林的,所以可能有很多棵樹)。
bz[x]^=1是什麼意思呢?
因為換了根之後,原來的根節點到x的深度都會進行翻轉。所以要打乙個標記。
但是翻轉之後不會影響別的節點嗎?
不會,因為access(x)之後,對根節點到x的路徑上的點操作,只有根節點到x的路徑上的點會被影響到。
旋轉
void rotate(int x)
void splay(int x,int y)
}
把x旋轉為y的子節點,splay的基本操作。
不過這裡rotate要注意乙個問題,因為旋轉時會影響pfa,所以要更新pfa。
不會詳見splay學習小記
標記下傳
void down(int x)
}void remove(int x,int y)while(x!=y);
while(d[0])down(d[d[0]--]);
}
這裡只有翻轉的標記下傳,具體的據題目的不同而不同。不會詳見splay學習小記
更新節點的值
void update(int x)
據題目的不同而不同。
連線兩顆子樹
乙個節點也把他當做乙個子樹。
void link(int x,int y)
這裡的x和y是分先後的,y深度大於要為x的深度父親。不過假如題目並沒有深度限制,比如說是乙個圖的建邊,那麼x,y就不分先後,比如說在魔法森林這題。
x一定要為當前那棵樹的根節點才能連線,否則可能原本就有父親,一連就錯了。
斷開兩個節點的連邊
void cut(int x,int y)
先讓x變為根節點(那麼x的深度就比y小了)才方便將x與y放入同一棵子樹中。然後將y旋轉為這棵子樹的根節點,因為x深度比y小,那麼x肯定在y的左邊,斷開連邊就好了。注意要把pfa清空。
找到原來的父親
int getfa(int x)
與splay中找左右節點的原理一樣。
對樹中x節點到y節點進行操作
例如求極值。
方法1《推薦》
int find(int x,int y)
像刪除一樣,首先把x到y都放入同一棵子樹中,因為x可能不是根節點,所以需要把其中乙個旋轉為根節點,我選擇y(也可以選x),然後在得出答案。
方法2
int find(int x,int y)
}
現在就用到access中的lca了,這個方法比較機智。因為在access(x)之後,access(y)中,最後一次的pfa[x]=0的x一定是lca,因為此時lca與根節點已經全是偏愛邊,所以pfa[lca]=0,而y又會跳到lca,然後就統計答案就好了。
插入或刪掉x到y的一條鏈
原理很簡單,就像序列上的插入修改一樣,找到x的兒子和y的兒子,然後,把x到y這段區間旋轉到一棵子樹上,然後斷去連邊就好了。不會詳見splay學習小記
由於用的不多就不貼標了。
翻轉與旋轉
這些操作都類似序列上的操作。不會詳見splay學習小記
翻轉上面講過了。
目前也只知道這麼多了,但是這些操作在大部分題目中都夠用了。
樹的統計
染色魔法森林
Link Cut Tree學習小記
lct幾個月前就學了,花了我一整天問alan cty才搞會 lct的左右就是維護樹上的一些值,和樹鏈剖分有相同的作用,也有超過樹鏈剖分的作用 lct也就是動態樹,意思就是樹是會動的,也就是有連邊和刪邊兩個操作 和鏈剖一樣,邊可以分為兩種,重邊 偏愛邊 和輕邊。一條重邊連起來的是一條鏈,在同一棵spl...
Link Cut Tree學習總結
title link cut tree學習總結 categories 演算法 date 2016 1 14 21 30 00 tags lct,演算法 lct是一種用於解決動態樹問題的資料結構。大體上的感受,lct就是樹鏈剖分和splay的結合版,什麼意思呢?因為要動態維護樹的結構和樹上的資訊,所以...
Link Cut Tree 學習筆記
link cut tree,用來解決動態樹問題。巨集觀上,lct維護的是森林而非樹。因此存在多顆lct。有點像動態的樹剖 鏈的確定通過 access 操作 每條鏈用一顆 splay 維護。splay 維護鏈的關鍵字是深度,因此一條鏈的頂端就是 splay 中鍵值最小的點 由於lct的資料有很多,在此...