樹上啟發式合併總結

2021-09-02 06:05:15 字數 1748 閱讀 7121

某一天發現一道樹上啟發式合併裸題,但我不會寫……

學習並刷了兩天的題,是時候來寫個總結了

樹上啟發式合併(dsu on tree),是乙個在o(n

logn

)o(nlogn)

o(nlog

n)時間內解決許多樹上問題的有力演算法。

但它的中心其實是——暴力!

沒錯,它正是由暴力優化而來。

我們先看一道例題:cf600e lomsat gelral

題意簡述:一棵樹有n個結點,每個結點都是一種顏色,每個顏色有乙個編號,求樹中每個子樹的最多的顏色編號的和。

我們先思考暴力:對於每個節點,暴力遍歷子樹,將它們的資料統計出來得到當前節點的答案,然後再暴力將這棵子樹的資料清空,以免影響到別的節點。

很明顯,這個做法是o(n

2)

o(n^2)

o(n2)的。

有的同學可能會有疑問:為什麼要清空呢?

\;由於空間的限制,我們不可能對於每乙個節點開乙個陣列來記錄資料,只能開乙個全域性陣列。

在這個全域性陣列內,如果不清空,就會影響到別的子樹,於是導致答案錯誤。

然而可以發現,統計兒子節點時最後那個節點其實沒有必要清空,因為它不再會影響到它的兄弟節點。

這也正是接下來要講到的優化方法。

思考優化:對於節點x,可以在做子樹答案時保留最後一棵子樹v的資料不清空,然後統計x的答案時繞過v節點統計別的子樹。那麼v選哪個呢?當然是選size最大的。

於是,我們得到了乙個優化後的做法:對於節點x,先統計輕兒子的答案,並將它們的資料清除;然後統計重兒子的答案,保留資料;最後遍歷其他輕兒子及其子樹,把它們的資料與重兒子合併。

非常神奇的是,經過分析,可以證明它的複雜度是o(n

logn

)o(nlogn)

o(nlog

n)的!(然而我不會證明,也懶得學)

回到例題,這正是可以用這種方法簡單解決的。放**:

bool s[sz]

;//是否是重兒子

int cnt[sz]

;//每種顏色出現次數

ll sum[sz]

,top;

//每個次數之和,以及最多的次數

void

add(

int c,

int t)

void

add(

int x,

int fa,

int t)

ll ans[sz]

;void

dfs(

int x,

int fa,

bool keep)

//keep:是否保留當前子樹的資料

經過上面的講解,相信各位已經大概明白了樹上啟發式合併的思路。接下來還有幾道練習題(位址均為洛谷題庫,要去原oj請通過洛谷上的鏈結過去):

cf570d tree requests

cf208e blood cousins

cf246e blood cousins return

cf1009f dominant indices

cf375d tree and queries

cf741d arpa』s letter-marked tree and mehrdad』s dokhtar-kosh paths

洛谷上均有對應的樹上啟發式合併題解,有一些是我的,可以點讚。(不要臉地騙個贊)

如果還不太明白,可以去這個部落格看。(然而是英文的)

總結 樹上啟發式合併

這種演算法能夠解決關於詢問一棵樹的子樹的相關資訊的問題。演算法的流程大概是這樣 1 dfs將一棵樹建好,將節點的size dfs序 重兒子 該dfs序對應的節點這些資訊處理好 其他的資訊具體問題具體分析 2 進入solve函式,先去解決非重兒子,然後將這些非重兒子的資訊暴力清空。3 接下來解決重兒子...

樹上啟發式合併

解決樹上統計問題,o n log n o n log n o n lo g n 可以結合線段樹等資料結構維護深度上的資訊 部落格 入門題 const int maxn 1e5 7 const int mod 1e9 7 ll n,m,u,v,mx,sum vector int mp maxn int...

樹上啟發式合併

樹上啟發式合併,一種美妙的黑科技,可以用普通的優化讓你 n 2 變成嚴格 n log 解決一些類似 樹上數顏色,樹上查眾數 這樣的問題 首先你要知道暴力為什麼是 n 2 的 以這個圖為例 每次你從乙個節點開始向下搜,你從1節點搜到3,搜完這個子樹然後你需要把3存的col等資訊刪去再遍歷另乙個子樹才是...