树形DP - 换根
例题 - Tree Painting CF - 普及+/提高 | |
例题 - 请努力完成本题,再阅读以下内容。 | 解答 |
换根DP的基本思路
所谓“换根”,指的是这样一种特定的解树上题目的技巧:
- 固定一个方便计算的根的位置,然后DP计算题目需要的答案。
- 计算将根移动到孩子处的时候,答案会怎样变化。然后DFS将根移动到所有位置,求最优结果。
这样的主要好处,是“换根”过程是利用树的结构的一个过程,使得之前的计算结果可以重用,降低复杂度。
题解 - Tree Painting
观察此题,首先可以看到第一个点选在哪里是比较关键的,因为后面的点必须和这个点相邻。实际上第一个点选定之后,最终的得分就确定了。
如果我们把第一点看成是树的根的话,设每个节点的子树大小是,可以得到整根树的总分是.
比较直观的想法,是对每个节点做这个计算,取最大值就得到了最后的答案。但是因为,这个的算法太慢了。
所以我们考虑用换根DP:
- 不失一般性,我们称把根固定在节点处,从这里开始画黑色节点,得到总分为.
- 假设已知,我们移动根到的孩子处,那这时一些会发生变化,我们想办法算出。
- 具体而言,我们看到只有和发生变化,如下图所示:
因此我们有:i u / \ / | \ u ... ==> (不变)... i / \ | ... ... (不变)
利用最后(4)这个公式,可以一遍DFS算出所有的,取最大值就是答案了。最终的复杂度是。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pi;
int n;
vector<int> adj[200005];
int S[200005];
ll f[200005];
void dfs1(int s, int e) { // 计算f[0]
S[s] = 1;
for (int u: adj[s]) {
if (u == e) continue;
dfs1(u, s);
S[s] += S[u];
}
f[0] += S[s];
}
void dfs2(int s, int e) { // 基于f[0], 计算所有的f[s]
if (s != 0)
f[s] = f[e] + n - 2*S[s];
for (int u: adj[s]) {
if (u == e) continue;
dfs2(u, s);
}
}
int main() {
scanf("%d", &n);
for (int i = 0; i < n-1; i++) {
int u,v;
scanf("%d %d", &u, &v);
u--, v--;
adj[u].push_back(v);
adj[v].push_back(u);
}
dfs1(0, -1);
dfs2(0, -1);
ll ans = *max_element(f, f+n);
printf("%lld", ans);
return 0;
}
习题
习题 | ||||||
---|---|---|---|---|---|---|
AC | Subtree | 提高+/省选- | 解答 | |||
P4268 | USACO Gold | Directory Traversal | 2018 | 提高+/省选- | 解答 |