0%

CF600E Lomsat gelral

Lomsat gelral - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

Solution

树上启发式合并模板题。

考虑暴力做法:设以 ii 为根的子树的答案为 AiA_i,每次求 AiA_i 都重新遍历整棵子树。选择从 11 开始往下 dfs,然后从子结点开始一个一个地求 AiA_i,时间复杂度为 O(n2)O(n^2)

定义与 ii 父亲相同(同为 fif_i)且深度相同的结点为 ii 的兄弟结点,设为 jj。不妨设 AiA_i 的求解在 AjA_j 前面,则求 AjA_j 时,需要把求 AiA_i 时所用的计数器等变量重新清零。

注意到作为 fif_i 的儿子,AiA_iAjA_j 都与 AfiA_{f_i} 有关联。显然最后一个求 AiA_ifif_i 的子结点在求完 AiA_i 后计数器不用清零,可直接用于 AfiA_{f_i} 的计算中。那么我们选择 fif_i 的重儿子作为最后一个求 AiA_i 的点。

换句话说,计算 AiA_i 时,不用遍历 ii 的重儿子的子树,轻儿子的子树要全部遍历。优化后,时间复杂度变为 O(nlogn)O(n\log n)。具体证明可见这里

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const ll N = 1e5 + 10;

ll n, c[N], siz[N], son[N], ans[N], cnt[N], maxn, sum;
vector<ll> g[N];

void clear(ll u, ll fa)
{
cnt[c[u]] = 0;
for (ll v : g[u]) {
if (v == fa) continue;
clear(v, u);
}
}

void calc(ll u, ll fa, ll s)
{
cnt[c[u]]++;
if (cnt[c[u]] > maxn) {
maxn = cnt[c[u]];
sum = c[u];
} else if (cnt[c[u]] == maxn)
sum += c[u];
for (ll v : g[u]) {
if (v == fa || v == s) continue;
calc(v, u, s);
}
}

void dfs1(ll u, ll fa)
{
siz[u] = 1;
for (ll v : g[u]) {
if (v == fa) continue;
dfs1(v, u);
if (siz[v] > siz[son[u]])
son[u] = v;
siz[u] += siz[v];
}
}

void dfs2(ll u, ll fa)
{
for (ll v : g[u]) {
if (v == fa || v == son[u]) continue;
dfs2(v, u);
maxn = sum = 0;
clear(v, u);
}
if (son[u])
dfs2(son[u], u);
calc(u, fa, son[u]);
ans[u] = sum;
}

int main()
{
scanf("%lld", &n);
for (int i = 1; i <= n; i++)
scanf("%lld", &c[i]);
for (int i = 1; i < n; i++) {
ll x, y;
scanf("%lld%lld", &x, &y);
g[x].push_back(y);
g[y].push_back(x);
}
dfs1(1, 0); dfs2(1, 0);
for (int i = 1; i <= n; i++)
printf("%lld ", ans[i]);
return 0;
}