跳转至

树上启发式合并

例题

给定一棵树,第 \(i\) 个节点颜色为 \(c_i\)。你需要求出对于每个节点 \(u\),以 \(u\) 为根的子树中出现次数最多的颜色是哪个。

\(n,c_i\le 10^5\)

我们可以用桶数组记录每种颜色出现的次数。但是空间不够给每个点都开一个桶,而转移又需要所有直系子节点的桶。这时就可以考虑使用树上启发式合并。

树上启发式合并只需要开 \(1\) 个全局的桶数组,但需要编写 \(2\)dfs 函数。dfs1(int u) 会统计 \(u\) 子树内的答案;dfs2(int u) 会将 \(u\) 子树内所有颜色出现的次数直接累加在桶数组上,而不更新答案。

我们有一种很暴力的做法:在 dfs1(u) 中循环给每个子树调用 dfs1,更新子树的答案之后,调用 dfs2 得到节点 \(u\) 的桶并更新答案。这种算法的时间复杂度为 \(O(n^2)\),但可以进一步优化。

注意到节点 \(u\) 在最后一次调用 dfs1(v) 时,可以控制 dfs1(v) 让其保留计算得到的桶,这样就无需调用 dfs2(v) 了。我们可以通过调整使得节点数量最多的子树(重儿子)总在最后调用并保留结果,就可以最小化 dfs2(v) 的计算次数。这种优化看似没有减小时间复杂度,但实际上可以直接将时间复杂度优化至 \(O(n\log n)\)

我们注意到 dfs1(u) 只会对所有轻边调用 dfs2(v)。这样每个节点被 dfs2 计算到的次数就是返根链中轻边的条数。注意到轻边的条数的上限是 \(log_2n\),因此总时间复杂度是 \(O(n\log n)\)

目前没有例题...