换根dp
概念
换根 \(dp\) ,又被称为二次扫描,是属于树形 \(dp\) 的一类但比一般树形dp更难。
特点
-
通常是没有指定根结点,且根结点的变化会对一些值产生影响。
-
通常需要两次 \(dfs\) ,第一次 \(dfs\) 预处理信息,第二次 \(dfs\) 开始换根动态规划。
-
求解的答案通常需要结合所有相连的结点,且一般都是多次询问某个点的答案。
优点
暴力求解的话,枚举每个结点作为根的情况再 \(dfs\) 扫描,这样就需要 \(O(n^2)\) 的时间复杂度,通常是不能够接受。而换根 \(dp\),先进行一次扫描预处理信息后,再一次扫描进行动态规划解出所有节点的答案,时间复杂度优化为 \(O(n + n)\) ,这样就可以成功获取答案了。
解法一般形式
换根 \(dp\) 第一次扫描通常需要结合树形 \(dp\) 的思想,先任选一个结点 \(root\) 作为根结点,然后从根结点开始递归处理信息,但这时只有以 \(root\) 作为根结点的信息,所以需要在第二次扫描时,考虑换另一个结点为根时的答案,这时要通过第一次预处理出来的信息进行状态转移。
即:
-
以某个点(通常是 \(1\))作为根节点进行第一次扫描,预处理信息。
-
依旧从这个点开始第二次扫描,但这次进行换根的动态规划,通常是结合父节点的信息合并统计答案。
题目讲解
树的中心
题目链接:AcWing 树的中心、洛谷 树的中心
题目大意:
给定一棵树,求这个树的中心。
树的中心:树上某个结点到最远的结点距离最近,那么这个结点就是树的中心。
思路:
首先建立以 \(1\) 为根的树,然后思考每个结点的最长距离会出现的路径:当前结点从某个子节点出发的最长路径,或从父节点出发的不再经过自己的最长路径。
所以,我们可以先第一次 \(dfs\) 扫描出每个结点从子结点出发的最长距离和次长距离,第二次扫描时结合父节点的数据更新当前结点为根时的最长距离。
处理次长距离的原因是:在进行换根的动态转移时,要结合父节点的最长路径,但如果父节点的最长路径恰好经过了当前结点,那么就要用父节点的次长路径来进行状态转移了。
AcWing代码
#include <iostream>
#include <cstring>
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 2e4 + 10;
int n;
int h[N], e[N], w[N], ne[N], idx;
int d1[N], d2[N], up[N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}
void dfs_d(int u, int fa)
{
d1[u] = d2[u] = -INF;
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (j == fa) continue;
dfs_d(j, u);
if (d1[j] + w[i] > d1[u]) d2[u] = d1[u], d1[u] = d1[j] + w[i];
else if (d1[j] + w[i] > d2[u]) d2[u] = d1[j] + w[i];
}
if (d1[u] == -INF) d1[u] = d2[u] = 0;
}
void dfs_u(int u, int fa)
{
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (j == fa) continue;
up[j] = up[u] + w[i];
if (d1[j] + w[i] != d1[u]) up[j] = max(up[j], d1[u] + w[i]);
else up[j] = max(up[j], d2[u] + w[i]);
dfs_u(j, u);
}
}
int main()
{
memset(h, -1, sizeof h);
cin >> n;
for (int i = 1; i < n; i ++)
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, c), add(b, a, c);
}
dfs_d(1, -1);
dfs_u(1, -1);
int res = INF;
for (int i = 1; i <= n; i ++) res = min(res, max(d1[i], up[i]));
cout << res;
return 0;
}
洛谷代码
#include <iostream>
#include <cstring>
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 2e5 + 10;
int n;
int h[N], e[N], ne[N], idx;
int d1[N], d2[N], up[N];
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
void dfs_d(int u, int fa)
{
d1[u] = d2[u] = -INF;
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (j == fa) continue;
dfs_d(j, u);
if (d1[j] + 1 > d1[u]) d2[u] = d1[u], d1[u] = d1[j] + 1;
else if (d1[j] + 1 > d2[u]) d2[u] = d1[j] + 1;
}
if (d1[u] == -INF) d1[u] = d2[u] = 0;
}
void dfs_u(int u, int fa)
{
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (j == fa) continue;
up[j] = up[u] + 1;
if (d1[j] + 1 != d1[u]) up[j] = max(up[j], d1[u] + 1);
else up[j] = max(up[j], d2[u] + 1);
dfs_u(j, u);
}
}
int main()
{
memset(h, -1, sizeof h);
cin >> n;
for (int i = 1; i < n; i ++)
{
int a, b;
cin >> a >> b;
add(a, b), add(b, a);
}
dfs_d(1, -1);
dfs_u(1, -1);
int res = INF;
int ans1, ans2 = -1;
for (int i = 1; i <= n; i ++) {
int d = max(d1[i], up[i]);
if (d < res) {
res = d;
ans1 = i;
}
else if (d == res) ans2 = i;
}
cout << ans1;
if (~ans2 && max(d1[ans2], up[ans2]) == res) cout << ' ' << ans2;
return 0;
}
[POI 2008] STA-Station
题目链接:洛谷 P3478
题目大意:
给定一个 \(n\) 个点的树,请求出一个结点,使得以这个结点为根时,所有结点的深度之和最大。
思路:
首先,我们依旧建立以 \(1\) 为根的树。定义 \(dp[i]\) 为:以 \(i\) 为根时深度之和。
接着,思考换根的状态转移,以样例距离

假设我们已经求出了以 \(1\) 为根的深度之和,那么当以 \(4\) 为根时,可以发现以 \(4\) 为根的子树的所有结点深度都减少了1,而不属于 \(4\) 为根的子树的结点的深度都增加了1,再举例以 \(5\) 为根时对比以 \(4\) 为根时也符合上面的推测,得出转移的公式:\(dp[v] = dp[u] - size[v] + n - size[v]\)
从上面的转移公式,可以知道我们第一次扫描需要预处理子树的结点个数和 \(dp[1]\) 的值。
点击查看代码
#include <iostream>
#include <vector>
using namespace std;
typedef long long ll;
const int N = 1e6 + 10;
int n;
vector<int> g[N];
ll c[N], dp[N], sum, ans;
void dfs1(int u, int fa, int h)
{
c[u] = 1;
dp[1] += h;
for (auto v : g[u]) {
if (v == fa) continue;
dfs1(v, u, h + 1);
c[u] += c[v];
}
}
void dfs2(int u, int fa)
{
for (auto v : g[u]) {
if (v == fa) continue;
dp[v] = dp[u] + n - 2 * c[v];
dfs2(v, u);
}
if (dp[u] > sum) {
sum = dp[u];
ans = u;
}
}
int main()
{
cin >> n;
for (int i = 1; i < n; i ++) {
int u, v;
cin >> u >> v;
g[u].emplace_back(v);
g[v].emplace_back(u);
}
dfs1(1, -1, 0);
dfs2(1, -1);
cout << ans;
return 0;
}

浙公网安备 33010602011771号