换根 DP
创建时间:2025-03-26
换根DP是一种特殊的树形DP,用于计算无根树中以任意一个节点为根时的最佳答案。
那么换根DP是怎么实现的呢?以洛谷P3478为例:
这道题要求出以哪个节点为根时所有节点的深度和最大。显然,如果给定的是一颗有根树,我们可以在 \(O(n)\) 的时间内求出深度和,这样可以得到一个时间复杂度为 \(O(n^2)\) 的算法:以每个节点为根都计算一次深度总和。
显然,这个做法在\(n\) 较大时速度极慢。考虑先假定任意一个节点(通常是节点1)为根DFS,求出以每个节点为根的子树里节点数量 \(siz_i\) 和的深度总和 \(dp_i\)。再跑第二遍DFS,维护数组 \(f_i\) 表示以 \(i\) 为整棵树的根的情况下所有节点的深度和,显然 \(f_1\) 应该等于 \(dp_1\),只需要考虑怎么转移 \(f\) 数组。对于 \(u\) 和 \(u\) 的儿子 \(v\),可以尝试将 \(v\) “提起来”,将 \(u\) “放下去”,即将这棵树的根换为 \(v\),并利用已有的以 \(u\) 为整棵树的根时的 \(f_u\) 计算 \(f_v\):
因为 \(v\) 被“提起来”成为了新的根,所以 \(v\) 原子树中的所有结点的深度和就等于 \(dp_v\);因为原来的根 \(u\) 被“放下去”,所以所有不在 \(v\) 原子树中的节点的深度都要+1,此时不在 \(v\) 原子树中的所有点的深度和 = \([f_u - (dp_v + siz_v)] + (n - siz_v)\) = \(f_u + n - dp_v - 2 \cdot siz_v\)。这样便得到了 \(f\) 的转移方程,即
有了 \(f\) 数组,我们便可以轻松的得到最终答案,代码如下:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int MAX_N = 1e6 + 50;
int n, dp[MAX_N];
int siz[MAX_N], f[MAX_N], ans;
vector<int> e[MAX_N];
void dfs1(int u, int fa) {
siz[u] = 1;
for (int v : e[u]) {
if (v == fa)
continue;
dfs1(v, u);
dp[u] += dp[v] + siz[v];
siz[u] += siz[v];
}
}
void dfs2(int u, int fa) {
for (int v : e[u]) {
if (v == fa)
continue;
f[v] = f[u] + n - siz[v] * 2;
dfs2(v, u);
}
}
signed main() {
cin >> n;
for (int i = 1, u, v; i < n; i++) {
cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
dfs1(1, 0);
f[1] = dp[1];
dfs2(1, 0);
for (int i = 1; i <= n; i++)
if (f[i] > f[ans])
ans = i;
cout << ans;
return 0;
}
根据这道题的解决方案,可以得出换根DP的使用条件:
- 给定的是一颗无根树,且要求以任意一个节点为根时的答案
- 可以快速的求出 \(u\)(父亲)到 \(v\)(儿子)的 \(f\) 变化量
下面是几道换根DP的例题:
洛谷P2986(模板题双倍经验);
CF1187E(换根DP建模);
CF1324F(换根DP建模);
洛谷P10974(边的流量限制);
洛谷P6419(换根DP+最长链);
洛谷P3761(枚举+树的直径+最长链)。

浙公网安备 33010602011771号