距离和换根
深度和·带权距离和
本课摘要
距离和换根课程回答“换根时全树距离和为什么只需常数时间更新”。内容以深度和·带权距离和为主线,配合逐步推导、可编辑演示、例题与练习形成可复查的学习闭环。
- 判断距离和换根的适用条件与状态边界
- 围绕“深度和·带权距离和”推导转移与计算顺序
- 用演示、复杂度分析和配套题目校验实现
本课目录 · 0 节
正在整理目录…
从「无权」到「带权」的距离和
上一节的「距离和」默认每条边长 1、每个点算 1。真实题目常常带两种权:点权(每个村庄住着不同人数 / 每个牧场有不同头数的牛)与边权(路的长短不一)。 目标变成:选一个集合点,使所有人赶来的总路程最小—— 也就是 最小,其中 是点 的人数、 是带边权的树上距离。
朴素解还是「枚举每个点当集合点,各跑一遍带权 BFS/最短路累加」——。 换根 DP 的思路完全不变,只是把「点数」换成「点权和」、把「1 步」换成「边权 步」。
换根系数:把「点数」升级成「点权和」
重新定义两个量: 是总点权; 改成 子树内点权之和(不再是节点数)。 第一遍后序:,并累加固定根的加权距离和 。
根从 挪到孩子 (这条边长 )时: 子树内那 的点权,每单位都近了 ; 其余 的点权,每单位都远了 。于是带权换根方程:
无权是它的特例: 时 、,方程退回 。
本质
距离和换根的通式是 : 是「有多少东西要移动」(点权和), 是「往 那边挪时有多少东西变近」, 是「每样东西挪动的步长」。 把这三者填对,无权 / 点权 / 边权就是同一份代码。
跟着算一遍
小例子:一条链 ,点权 (点 3 上住了 4 个人),边权都为 1。总点权 。固定根 1:
点节点,看距离和实时变
系数为负(子树内点权 > 一半)→ 往那边挪根更优; 为正→ 挪过去更差。顺着「负系数」的方向一路走,就走到重心。
n 小时:拿暴力给换根「对拍」
像「医院设置」这种 的题,暴力 (对每个点 BFS 累加)也能过。 这反而是好事:你可以先写暴力拿到分,再写换根 ,两者输出必须完全一致—— 这是验证换根系数没写错的最省心办法。等题目把 放大到 ,暴力挂了,你手里的换根已经拍过、可靠。
「医院设置」输入按二叉树的左右儿子给出,但换根不关心二叉不二叉——照样建无向邻接表,当一般树跑两遍 DFS 即可。
易错点
带权时 是子树点权和而非节点数——初值要写 ,不是 。 换根系数别忘了乘边权 。加权距离和更容易爆 ,全程 。
例题
#include <iostream>
#include <vector>
using namespace std;
typedef long long ll;
const int N = 100005;
int n;
struct E { int to; ll w; };
vector<E> g[N];
ll c[N]; // c[u]:点 u 的牛数(点权)
ll sz[N]; // sz[u]:子树内牛数之和(不是节点数!)
ll W; // 总牛数
ll f[N]; // f[u]:把 u 当集合点时的带权距离和
// 第一遍:sz[u] = 子树牛数和;f[1] 顺带累加(子树 c 走 w 到 1)
void dfs1(int u, int fa, ll dep)
{
sz[u] = c[u];
f[1] += c[u] * dep; // 以 1 为根:u 的牛各走 dep 到 1
for (E e : g[u])
{
if (e.to == fa) continue;
dfs1(e.to, u, dep + e.w);
sz[u] += sz[e.to];
}
}
// 第二遍:换根 f[v] = f[u] + w*(W - 2*sz[v])
void dfs2(int u, int fa)
{
for (E e : g[u])
{
if (e.to == fa) continue;
// 子树 v 的 sz[v] 头牛各近 w,其余 W - sz[v] 头牛各远 w
f[e.to] = f[u] + e.w * (W - 2 * sz[e.to]);
dfs2(e.to, u);
}
}
int main()
{
cin >> n;
W = 0;
for (int i = 1; i <= n; i++) { cin >> c[i]; W += c[i]; }
for (int i = 1; i < n; i++)
{
int a, b; ll w;
cin >> a >> b >> w;
g[a].push_back({b, w});
g[b].push_back({a, w});
}
dfs1(1, 0, 0);
dfs2(1, 0);
ll ans = f[1];
for (int i = 2; i <= n; i++) ans = min(ans, f[i]);
cout << ans << endl;
return 0;
}#include <iostream>
#include <vector>
using namespace std;
typedef long long ll;
const int N = 105;
int n;
ll c[N]; // 该点居民数
vector<int> g[N];
ll sz[N], f[N], W;
void dfs1(int u, int fa, ll dep)
{
sz[u] = c[u];
f[1] += c[u] * dep;
for (int v : g[u])
{
if (v == fa) continue;
dfs1(v, u, dep + 1); // 医院设置边权=1
sz[u] += sz[v];
}
}
void dfs2(int u, int fa)
{
for (int v : g[u])
{
if (v == fa) continue;
f[v] = f[u] + (W - 2 * sz[v]); // 无边权,系数即 W - 2*sz[v]
dfs2(v, u);
}
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
int l, r;
cin >> c[i] >> l >> r; // 居民数、左儿子、右儿子(0 表示无)
W += c[i];
if (l) { g[i].push_back(l); g[l].push_back(i); }
if (r) { g[i].push_back(r); g[r].push_back(i); }
}
dfs1(1, 0, 0);
dfs2(1, 0);
ll ans = f[1];
for (int i = 2; i <= n; i++) ans = min(ans, f[i]);
cout << ans << endl;
return 0;
}
