[常見作法整合]CSP-S2019 D2T3 樹的重心

CSP-S2019 D2T3 樹的重心(centroid)

本題解是題解欄內一些常見思路的集合。數組

爲了篇幅緊湊,在一些地方我可能跳過了證實/闡述的不是怎麼詳細,若是但願看到某一個思路的詳細闡述/代碼,能夠點擊相關的超連接。數據結構

思路

本題的部分分啓發咱們去找性質:spa

  1. 鏈的部分分啓發咱們去考慮樹鏈剖分
  2. 二叉樹的部分分啓發咱們去考慮以重心爲根的狀況。
  3. 因爲要同時考慮子樹與外子樹的狀況,這啓發咱們想到換根法

性質:blog

  1. 重心\(u\)在根節點所在的重鏈上。
  2. \(u\in son_{rt}\)\(v\)\(u\)的重心,則\(rt\)的重心爲\(v\)的祖先。
  3. \(u\)爲重心,只有\(u\)的重兒子(或父親)有多是重心。

計數方法:get

  1. 對於每個點,咱們計算它做爲重心的次數:這會致使實現偏向數據結構(BIT、可持久化線段樹、樹狀數組等)。
  2. 對於每個分割,咱們去找兩課樹的重心:這回致使實現偏向圖論類方法(樹鏈剖分、找重兒子、倍增等)。

方法0:考慮點,只考慮定義

代碼 相似思路的題解io

考慮一個點\(u\),咱們想要知道,它會成爲幾回重心。class

將樹在\(u\)處定根,設其最大的子樹大小爲\(S=siz_w\),則能夠分爲兩種狀況:二叉樹

  1. 刪去的邊不在\(tree_w\)中,\(tree_v\not\subseteq tree_w\):則\(siz_v\le n-2S\)時,\(u\)成爲重心。
  2. 刪去的邊在\(tree_w\)中:設除了\(tree_w\)最大的子樹大小爲\(T\),則\(2S-n\le siz_v\le n-2T\)\(u\)成爲重心。

所以,咱們考慮處理出以\(u\)爲根,各個兒子的\(\{siz\}\)的可重集合的狀況,再查詢一段區間內的\(siz\)的個數便可。方法

維護方法

能夠用可持久化線段樹(可能能夠線段樹合併/將全部查詢離線化)維護。數據

特別地,\(fa_u\)的狀況,至關於\(tree-anc_u-tree_u\)\(\{u\}+anc_u-\{rt\}\)取反。(其中,\(anc_u\)表示\(u\)的全部祖先節點)

方法1:考慮點,以重心爲根

咱們先找到重心\(rt\),並以它做爲根。這樣和隨意選根有什麼區別呢?

性質:若\(x\ne rt\)爲重心,則刪去的邊\((u,v)\)必定不在\(tree_x\)內。

所以,對一個點\(u\ne rt\),他做爲重心僅當刪去了一條邊\((x,y)\),且:\((x,y)\)不在\(tree_u\)內,\(n-2s_x\le S\le n-2g_x\)。(設樹減小的大小爲\(S\)

對於根的狀況,能夠另外(分紅割去的邊在重兒子子樹內與不在重兒子子樹內)判斷。

能夠用樹狀數組維護,具體見這篇題解

方法2:考慮邊

考慮一種刪邊狀況,咱們須要快速求出,劃分後的全部重心。

考慮如何求一棵樹的重心:由於重心必定在根節點所在重鏈上,從根一直跳重兒子,直到找到最深的\(v\)使得,\(2s_v > s_u\),則\(v\)(可能還有它的重兒子)爲重心。此過程能夠用倍增長速。

由這種作法,咱們能夠在樹鏈剖分,並預處理倍增數組以後\(O(\log n)\)地求出任何子樹的重心。

高贊題解的變體

咱們考慮一個劃分\((u,v)\),不妨設\(dep_u>dep_v\)

考慮如何求\(tree_u\)的重心:咱們從\(u\)出發,一直向重兒子跳,跳到找到重心(即,重兒子的子樹大小不超過原樹的一半)爲止。

考慮如何求出\(tree-tree_u\)的重心:咱們先將根換到\(v\)處(此時只改變了兩個節點的關係),這樣實際上就至關於求\(tree_u\)的重心!

具體實如今

方法3:考慮邊,某神奇的\(O(n)\)作法

就如以前所說的,能夠在\(O(n)\)計算出全部子樹的重心,可是很難對去掉子樹的部分找到一個重心單調移動的計算序列……

以重心爲根,考慮刪去的邊在哪棵子樹內:

  1. 不在重兒子的子樹內:則枚舉全部可能的子樹大小,在根所在的重鏈上面走就行了。
  2. 在重兒子的子樹內:
    1. 重兒子仍然爲重兒子:此時,根仍然爲重心。
    2. 次重兒子變爲了重兒子:在次重兒子的重鏈上走便可。

這樣就用純圖論方法完成了這題。

參考資料

相關文章
相關標籤/搜索