樹剖 LCA模板 對樹剖的再認識

2021-10-01 03:10:15 字數 3055 閱讀 4328

首先我們已經明確,樹鏈剖分是將樹上操作轉化為線性操作的高階暴力。

樹轉鏈的操作,我們可以通過兩遍dfs來實現。然後我們利用線段樹或樹狀陣列之類的來維護第二遍dfs序即可。

鑑於我已經兩年每寫過模擬之外的東西了(今天上午複習了spfa,這個另算),這次的重點放在了對dfs的理解上。

一開始想用結構體,後來又想到鄰接表,但兒子的個數不定而且用存圖的方法存樹未免有些奇怪~~(雖然樹也是圖的一種)~~ 所以最終敲定為vector儲存兒子,fa陣列儲存父親。

鑑於我們讀入時不知道父子關係,便先在儲存兒子的vector中都存著,在dfs中解決父子關係問題。

這個問題困擾了我好久。一開始想特判根,因為根的vector裡一定全是兒子,但後來發現其他兒子進了dfs後就不好處理了,於是想到,先在dfs之外預處理好根的情況,進了dfs以後,根和葉子就可以一視同仁了。

那麼我們的dfs肯定先從根開始。進入第一層dfs後我們遍歷根的vector(兒子們),一定全是兒子。這時候直接讓兒子們的fa為根,然後進兒子們的dfs就好。

進入到兒子們的dfs後,這些兒子們肯定都是有父親的,於是在遍歷兒子們的vector時,記得把fa剔除掉就好了。

寫一段偽**吧

for

(register

int i =

0; i < len; i++)if

(v != fa[x]

)

可以看到,對於任意節點x,它的所有兒子與它的父親都在vector裡,而v不是x的父親時,x就一定是v的父親,然後我們這個有父親的v可以繼續快樂的dfs下去了~耶!

第一遍dfs的核心任務就是統計兒子的個數並找出重兒子。那麼如何實現統計兒子的個數呢?

我們不妨假設,當我們對某個節點v的dfs後,我們已經知道了這個節點v的子樹中的全部節點數sz[v],那麼對於這個節點的父親x來說,把所有的sz[v]加起來,再加上自己,就是節點x的子樹中的全部節點數sz[x]。這樣同時就實現了完成dfs並統計兒子個數的任務。

偽**如下

sz[x]++;

for(

register

int i =

0; i < len; i++

)

第乙個++是自己,而且對於葉子來說它也沒兒子啊所以不會進入for迴圈的。

既然我們實現了「完成dfs的同時也把兒子個數給統計出來了」這樣的任務,尋找重兒子的工作也就迎刃而解了,加乙個if判斷一下就ok了。

if

(sz[v]

> sz[hs[x]])

hs[x]

= v;

作為乙個lca題,還是維護一下深度的好,畢竟,這次我們的dfs序連維護都不用維護的啊!!!跑兩遍就完事了orz

我們第二遍dfs之後得到的就是所需樹鏈了。對於鏈上的點,我們需要乙個top陣列稍加維護。這個top的意義何在?如同其字面意思,top記錄了每乙個樹鏈的頂端。當我們得到一串長長的dfs序後,我們並不清楚其中鏈的關係,而top則為我們指明了方向。對於一條鏈來說,深度最淺的點自然是整條鏈的top,而知道了乙個點的top,也就知道了它屬於哪條鏈。

靈魂畫師再度上線~

對於任意兩個節點x,y,如何找到他們的公共祖先呢?

首先是最簡單的情況,x,y在同一條鏈上,自然深度淺的是祖先。

如果不在同一條鏈上呢?

我們就想到,要是他們的某個祖先在同一條鏈上,就那公共祖先就找到了。

那找它那個祖先呢?用fa乙個個往上找自然是太慢了,如果用top顯然會快一點,例如上圖的a節點與2節點,用fa往上找和用top往上找顯然時間複雜度差了太多。但這也不是最優,畢竟我們知道了x,y不在同一條鏈上,那麼和x同一條鏈的top自然也和y不在同一條鏈上,索性直接跳到fa[top[x]]上更方便。

這樣我們也理解了為什麼根的父親是根自己。(如果年代久遠又理解不了了就再去康康上圖的a節點與2節點——致未來的自己)

**實現

inline

intlca

(int x,

int y)

if(dep[x]

> dep[y]

)return y;

return x;

}

解決了上述問題,最後就是全部的**了~

鼓掌~撒花~

#include

#include

using

namespace std;

inline

long

long

read()

return i * ans;

}int n, m, s;

int fa[

500100

], hs[

500100

], top[

500100

], sz[

500100

], dep[

500100];

vector <

int> son[

500100];

#define v son[x][i]

inline

void

dfs(

int x)

return;}

inline

void

dfs(

int x,

int r)

inline

intlca

(int x,

int y)

if(dep[x]

> dep[y]

)return y;

return x;

}int

main()

fa[s]

= s;

dfs(s)

;dfs

(s, s)

;for

(register

int i =

1; i <= m; i++

)return0;

}

其實只有90行,挺短的,不是麼~

樹鏈剖分(1) 樹剖求LCA

先看乙個樹剖的經典應用 初始化 先dfs一遍子樹,統計出每乙個點x的重兒子son x 和以x為根節點的子樹的大小siz x 這裡選擇x中子樹大小最大的兒子作為它的重兒子,第二遍dfs劃分樹鏈 重兒子與其父親節點劃分到一條鏈。其他的兒子為x的輕兒子,但屬於新的鏈的頂端元素。void dfs1 int ...

Lca樹鏈剖分法

樹鏈剖分各陣列的作用 son 最重的兒子節點,即節點最多的那個 bulk 所有兒子節點的總和 包括兒子的兒子 dep 該節點的深度 ft 該節點的父親 top 鏈的最上方的節點 首先dfs預處理出前面的四個陣列。然後第二個dfs進行剖分,預處理出最後乙個陣列。例子 此時son 1 3 因為bulk ...

LCA 樹鏈剖分

剛打完lca板子,寫個東西記下 dfs第一遍求出 結點i的深度,以i為根的子樹大小,結點i的父親,並求出重鏈 dfs第二遍求出 結點i所在重鏈的鏈頂 如果在重鏈上 開始lca,兩個點往上找,深度大的點就往上跳,這個點如果在重鏈上,就跳到所在重鏈的鏈頂的父親處,在輕鏈上,就直接跳到自己父親上 洛谷lc...