某一天發現一道樹上啟發式合併裸題,但我不會寫……
學習並刷了兩天的題,是時候來寫個總結了
樹上啟發式合併(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等資訊刪去再遍歷另乙個子樹才是...