樹上啟發式合併(dsu on tree)精巧的暴力

2022-09-09 20:42:29 字數 1847 閱讀 4792

雖然叫dsu但這和並查集貌似沒什麼關係

例:

給你一棵樹,每個節點有乙個顏色,要求出每個子樹中數量最多的顏色並輸出

(數量相同的情況先不考慮 不重要)

當我們需要在每個子樹上統計一些資訊的時候,往往會開乙個全域性的cnt陣列,試圖 dfs \(o(n)\) 掃一遍,一邊加點一邊得到答案

但對於一棵樹而言顯然有問題:當我們統計完其左子樹的資訊後,必須清空整個cnt陣列才能去掃右子樹,這樣其實就已經變成 \(o(n^2)\) 了

當然我們可以稍微偷工減料一點,因為最後一棵子樹統計完後不用清空,我們可以最後遍歷最大的那棵子樹

最大子樹可以通過一遍dfs預處理出子樹的size,記錄每個點的重兒子得到(類似樹剖)

然而就是這一點偷工減料,使得整個演算法複雜度直接降至 \(o(nlogn)\)

如果不關心證明的話,你已經學會 dsu on tree 了

證明為什麼這樣瞎搞就能獲得\(nlogn\)​的複雜度:

以下通過感性理解的方式說明為什麼這東西能優化這麼多

回顧一下在每個節點處我們要做什麼:

前兩步的操作一共是 \(o(n)\) 的,就是最樸素的從頭到尾掃一遍

現在需要考慮:在每個點處對每個輕子樹掃一遍的複雜度

如果乙個點和根節點之間一共有 x 條輕邊,那麼它會被遍歷差不多 x+1 次

而輕重鏈剖分有個很好的性質:走一條輕邊時,節點數量至少被砍一半,否則這就不是輕邊了

那麼從根節點到任意節點經過的輕邊數量最多是 \(logn\)​ 級別的

所以其實很顯然了:複雜度就是 \(o(nlogn)\)

再看看極端情況加深理解:

樹上問題最容易被出題人的各種鏈,菊花圖,鏈加菊花圖啥的卡掉

如果這棵樹長得像鏈,它將被最後走最大子樹這一小貪心優化掉一大半;

如果這棵樹長得像菊花圖,,那麼根節點到任意節點間的輕邊數量都將是極少的;

所以你可以相信dsu on tree

**(這道題的)

int n;

int col[maxn];

int cnt[maxn];

ll ans[maxn];

int siz[maxn], son[maxn];

struct edgee[maxn*2];

int hd[maxn], ecnt = 0;

inline void add(int x, int y)

void dfs1(int p, int fa)

}}ll tot = 0, mxc = 0;

void addcol(int c, int ad)else if(cnt[c] == mxc)

}void cntall(int p, int fa, int d)

}addcol(col[p], d);

}void dfs(int p, int fa, int s**)

}if(son[p]) dfs(son[p], p, 1);

for(int i=hd[p];i;i=e[i].nt)

}//此時所有子節點均已記錄

addcol(col[p], 1);

ans[p] = tot;

if(!s**) cntall(p, fa, -1), tot = mxc = 0;

}void solve()

dfs1(1, -1);

dfs(1, -1, 1);

for(int i=1;icout << ans[n] << '\n';

}

樹上啟發式合併

解決樹上統計問題,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等資訊刪去再遍歷另乙個子樹才是...

樹上啟發式合併總結

某一天發現一道樹上啟發式合併裸題,但我不會寫 學習並刷了兩天的題,是時候來寫個總結了 樹上啟發式合併 dsu on tree 是乙個在o n logn o nlogn o nlog n 時間內解決許多樹上問題的有力演算法。但它的中心其實是 暴力!沒錯,它正是由暴力優化而來。我們先看一道例題 cf60...