Something about 樹鏈剖分

Tenderfoot發表於2021-08-25

宣告:部分思路與圖片源於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)$條重鏈。

 (待更新)

相關文章