宣告:部分思路與圖片源於OI Wiki
關於樹鏈剖分
樹鏈剖分用於將樹分割成若干條鏈的形式,以維護樹上路徑的資訊。
樹鏈剖分有多種形式,如 重鏈剖分,長鏈剖分 和用於 $LCT$ 的剖分,大多數情況下,“樹鏈剖分”都指“重鏈剖分”。
重鏈剖分可以將樹上的任意一條路徑劃分成不超過$O(\log n)$條連續的鏈,每條鏈上的點深度互不相同(即是自底向上的一條鏈,鏈上所有點的$LCA$為鏈的一個端點)。
重鏈剖分還能保證劃分出的每條鏈上的節點$DFS$序連續,因此可以方便地用一些維護序列的資料結構(如線段樹)來維護樹上路徑的資訊。
如:
1.修改 樹上兩點之間的路徑上 所有點的值。
2.查詢 樹上兩點之間的路徑上 節點權值的 和/極值/其它(在序列上可以用資料結構維護,便於合併的資訊)
除了配合資料結構來維護樹上路徑資訊,樹剖還可以用來$O(\log n)$(且常數較小)地求$LCA$。在某些題目中,還可以利用其性質來靈活地運用樹剖。
重鏈剖分
給出以下定義:
定義 重子節點 表示其子節點中子樹最大的子結點。如果有多個子樹最大的子結點,取其一。如果沒有子節點,就無重子節點。
定義 輕子節點 表示剩餘的所有子結點。
從這個結點到重子節點的邊為 重邊。
到其他輕子節點的邊為 輕邊。
若干條首尾銜接的重邊構成 重鏈。
把落單的結點也當作重鏈,那麼整棵樹就被剖分成若干條重鏈。
實現
做出以下說明:
$Ftr_x$表示節點$x$在樹上的父親。
$Dep_x$表示節點$x$在樹上的深度。
$Size_x$表示節點$x$的子樹的節點個數。
$Son_x$表示節點$x$的重兒子。
$Top_x$表示節點$x$所在 重鏈 的頂部節點(深度最小)。
$Dfn_x$表示節點$x$的 $DFS$ 序,也是其線上段樹中的編號。
$Rank_x$表示 $DFS$ 序所對應的節點編號,有$Rank_{Dfn_x}=x$ 。
我們進行兩遍 DFS 預處理出這些值,其中第一次$DFS$求出 $Ftr_x$,$Dep_x$,$Size_x$,$Son_x$,第二次 DFS 求出 $Top_x$,$Dfn_x$,$Rank_x$。
inline void DFS1(int o) { Son[o] = -1; Size[o] = 1; for (register int j = h[o]; j; j = nxt[j]) if (!Dep[p[j]]) { Dep[p[j]] = Dep[o] + 1; Ftr[p[j]] = o; DFS1(p[j]); Size[o] += Size[p[j]]; if (Son[o] == -1 or Size[p[j]] > Size[Son[o]]) Son[o] = p[j]; } } inline void DFS2(int o , int t) { Top[o] = t; Dfn[o] = ++Cnt; Rank[Cnt] = o; if (Son[o] == -1) return; DFS2(Son[o] , t); for (register int j = h[o]; j; j = nxt[j]) if (p[j] != Son[o] and p[j] != Ftr[o]) DFS2(p[j] , p[j]); }
重鏈剖分性質
樹上每個節點都屬於且僅屬於一條重鏈。
重鏈開頭的結點不一定是重子節點(因為重邊是對於每一個結點都有定義的)。
所有的重鏈將整棵樹 完全剖分。
在剖分時重邊優先遍歷,最後樹的$DFN$序上,重鏈內的$DFN$序是連續的。按$DFN$排序後的序列即為剖分後的鏈。
一顆子樹內的$DFN$序是連續的。
可以發現,當我們向下經過一條 輕邊 時,所在子樹的大小至少會除以二。
因此,對於樹上的任意一條路徑,把它拆分成從$LCA$分別向兩邊往下走,分別最多走$O(\log n)$次,因此,樹上的每條路徑都可以被拆分成不超過$O(\log n)$條重鏈。
(待更新)