原题链接:E. Tree Painting
题目大意:
给定一棵 n n n 个点, n − 1 n-1 n−1 条边的树,最初时所有节点都是白色的,你要进行 n n n 次染色使得整棵树都变成黑色。
操作规则如下:
- 开始时,你可以任选一个点染成黑色。
- 之后,你可以任选一个与黑色的节点相连白色节点,将其染成黑色,并获得该节点被染色前所在白色连通块大小的权值。
问你进行操作后能获得的权值之和最大为多少。
解题思路
通过手摸样例我们发现,答案在我们第一步确定第一个黑色节点时就已经固定了,和操作顺序无关,那我们只需要计算选哪一个点作为第一个黑色节点最大即可。
考虑怎么计算选点后的权值之和,假设 s o n u son_u sonu 为节点 u u u 的儿子节点集合, s i z u siz_u sizu 为节点 u u u 的子树大小, d p u dp_u dpu 为子树对答案的贡献之和。
那么递推式就是:
d p [ u ] = s i z [ u ] + ∑ v ∈ s o n u d p [ v ] \begin{array}{c} dp[u]= siz[u]+\sum_{v \in son_u}dp[v] \end{array} dp[u]=siz[u]+∑v∈sonudp[v]
这样,我们用一个 D F S DFS DFS 就可以在 O ( n ) O(n) O(n) 的时间内把选 u u u 点作为根节点的总答案 d p [ u ] dp[u] dp[u] 计算出来了。
但是这样我们只计算了 u u u 作为根节点的答案,对于其他点作为根节点的答案我们还不知道,而枚举所有点的复杂度是 O ( n 2 ) O(n^2) O(n2) 的,不可接受。
如果我们能够用 d p [ u ] dp[u] dp[u] 的答案计算 d p [ v ] dp[v] dp[v] 的答案就好了。
引入知识点:换根 D P DP DP !
假设我们已经计算好了 d p [ 1 ] dp[1] dp[1] 的值,接下来要计算 d p [ 4 ] dp[4] dp[4] 的值,想想我们要怎么让答案从 d p [ 1 ] dp[1] dp[1] 转移到 d p [ 4 ] dp[4] dp[4]。(下图)
我们假设 g u g_u gu 为每个点 u u u 作为根的最终答案,那么显然
g [ 1 ] = d p [ 1 ] \begin{array}{c} g[1]=dp[1] \end{array} g[1]=dp[1]
假设我们要计算 g [ 4 ] g[4] g[4] ,我们假设 4 4 4 为此时的根,推推式子:
设 d p ′ [ 1 ] dp'[1] dp′[1] 是以 4 4 4 作为根, 1 1 1 的子树对答案的贡献, s i z ′ [ 1 ] siz'[1] siz′[1] 为以 4 4 4 为根时 1 1 1 的子树大小, s i z ′ [ 4 ] siz'[4] siz′[4] 为以 4 4 4 为根时整棵树的大小,那么我们考虑将 g [ 4 ] g[4] g[4] 从以 g [ 1 ] g[1] g[1] 转化过来。
g
[
4
]
=
s
i
z
′
[
4
]
+
∑
v
∈
s
o
n
4
d
p
[
v
]
+
d
p
′
[
1
]
\begin{array}{c} g[4]= siz'[4]+\sum_{v \in son_4}dp[v] + dp'[1] \end{array}
g[4]=siz′[4]+∑v∈son4dp[v]+dp′[1]
考虑怎么快速算出
d
p
′
[
1
]
dp'[1]
dp′[1] 的值。
d
p
′
[
1
]
=
s
i
z
′
[
1
]
+
∑
v
∈
s
o
n
1
∣
v
∉
4
d
p
[
v
]
=
s
i
z
′
[
1
]
+
∑
v
∈
s
o
n
1
d
p
[
v
]
−
d
p
[
4
]
\begin{array}{c} dp'[1]=siz'[1]+\sum_{v \in son_1 | v \notin4}dp[v]\\ =siz'[1]+\sum_{v \in son_1} dp[v]-dp[4] \end{array}
dp′[1]=siz′[1]+∑v∈son1∣v∈/4dp[v]=siz′[1]+∑v∈son1dp[v]−dp[4]
注意到:
s
i
z
′
[
1
]
=
n
−
s
i
z
[
4
]
=
s
i
z
[
1
]
−
s
i
z
[
4
]
siz'[1]=n-siz[4]=siz[1]-siz[4]
siz′[1]=n−siz[4]=siz[1]−siz[4]
d
p
′
[
1
]
=
s
i
z
′
[
1
]
+
∑
v
∈
s
o
n
1
d
p
[
v
]
−
d
p
[
4
]
=
s
i
z
[
1
]
−
s
i
z
[
4
]
+
∑
v
∈
s
o
n
1
d
p
[
v
]
−
d
p
[
4
]
=
s
i
z
[
1
]
+
∑
v
∈
s
o
n
1
d
p
[
v
]
−
d
p
[
4
]
−
s
i
z
[
4
]
=
g
[
1
]
−
d
p
[
4
]
−
s
i
z
[
4
]
\begin{array}{c} dp'[1]=siz'[1]+\sum_{v \in son_1} dp[v]-dp[4]\\ =siz[1]-siz[4]+\sum_{v \in son_1} dp[v]-dp[4]\\ =siz[1]+\sum_{v \in son_1} dp[v]-dp[4]-siz[4]\\ =g[1]-dp[4]-siz[4] \end{array}
dp′[1]=siz′[1]+∑v∈son1dp[v]−dp[4]=siz[1]−siz[4]+∑v∈son1dp[v]−dp[4]=siz[1]+∑v∈son1dp[v]−dp[4]−siz[4]=g[1]−dp[4]−siz[4]
带回原式:
g
[
4
]
=
s
i
z
′
[
4
]
+
∑
v
∈
s
o
n
4
d
p
[
v
]
+
d
p
′
[
1
]
=
s
i
z
′
[
4
]
+
∑
v
∈
s
o
n
4
d
p
[
v
]
+
g
[
1
]
−
d
p
[
4
]
−
s
i
z
[
4
]
\begin{array}{c} g[4] = siz'[4] + \sum_{v \in son_4}dp[v] + dp'[1]\\ = siz'[4] + \sum_{v \in son_4}dp[v]+g[1]-dp[4]-siz[4]\\ \end{array}
g[4]=siz′[4]+∑v∈son4dp[v]+dp′[1]=siz′[4]+∑v∈son4dp[v]+g[1]−dp[4]−siz[4]
注意到: s i z ′ [ 4 ] = n , d p [ 4 ] = s i z [ 4 ] + ∑ v ∈ s o n 4 d p [ v ] siz'[4]=n,dp[4]=siz[4]+\sum_{v \in son_4}dp[v] siz′[4]=n,dp[4]=siz[4]+∑v∈son4dp[v]
g [ 4 ] = s i z ′ [ 4 ] + ∑ v ∈ s o n 4 d p [ v ] + g [ 1 ] − d p [ 4 ] − s i z [ 4 ] = n + g [ 1 ] − s i z [ 4 ] − s i z [ 4 ] = g [ 1 ] + n − 2 × s i z [ 4 ] \begin{array}{c} g[4] = siz'[4] + \sum_{v \in son_4}dp[v]+g[1]-dp[4]-siz[4]\\ =n+g[1]-siz[4]-siz[4] \\ =g[1]+n-2 \times siz[4] \end{array} g[4]=siz′[4]+∑v∈son4dp[v]+g[1]−dp[4]−siz[4]=n+g[1]−siz[4]−siz[4]=g[1]+n−2×siz[4]
那么通式就是:
g [ v ] = g [ u ] + n − 2 × s i z [ v ] \begin{array}{c} g[v]=g[u]+n-2 \times siz[v] \end{array} g[v]=g[u]+n−2×siz[v]
( u u u 为与 v v v 相邻的,已经算出 g u g_u gu 的节点)
那么这样我们就算出了以 4 4 4 为根获得的答案 g [ 4 ] g[4] g[4],这样,我们还能继续算与 4 4 4 相邻的节点 g [ 9 ] g[9] g[9],算完 g [ 9 ] g[9] g[9] 还能再算与 9 9 9 相邻的节点 g [ 7 ] , g [ 8 ] g[7],g[8] g[7],g[8]…
这样,我们只需要先算出任意一个点为根的答案 g u g_u gu ,再用一个 D F S DFS DFS 就能将原图的所有以 v v v 为根的答案算出来了。
时间复杂度: O ( n ) O(n) O(n)
AC代码
#include <bits/stdc++.h>
#define NO return void(cout << "No\n")
#define YES return void(cout << "Yes\n")
using namespace std;
using i64 = long long;
using PII = pair<int, int>;
const int N = 2e5 + 10;
vector<int> G[N];
i64 g[N], dp[N], siz[N];
int n;
void DFS1(int u, int ufa) {
siz[u] = 1;
for (auto& v : G[u]) {
if (siz[v]) continue;
DFS1(v, u);
siz[u] += siz[v];
dp[u] += dp[v];
}
dp[u] += siz[u];
}
void DFS2(int u, int ufa) {
if (u != 1) {
//通式:
g[u] = g[ufa] + n - 2 * siz[u];
}
for (auto& v : G[u]) {
if (v == ufa) continue;
DFS2(v, u);
}
}
void solve() {
cin >> n;
for (int i = 1; i <= n - 1; ++i) {
int u, v; cin >> u >> v;
G[u].emplace_back(v);
G[v].emplace_back(u);
}
//随便取一个点先算出一个 g1 的值
DFS1(1, 0);
g[1] = dp[1];
//第二次利用 g1 的值 算出其他 gu 的值
DFS2(1, 0);
i64 ans = 0;
for (int i = 1; i <= n; ++i) {
ans = max(ans, g[i]);
}
cout << ans << '\n';
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int t = 1; //cin >> t;
while (t--) solve();
return 0;
}