Teleporter
Teleporter
换根DP,树 + 传送门 + 限制传送次数 k 的最短路求和
1. 问题重述
给定一棵 n 个节点的树(边权为正),以及 m 条零代价的传送边(双向)。定义 d(u, k) 为从 u 出发,使用传送边不超过 k 次,到达节点 1 的最小时间代价。要求对于每个 k = 0,1,...,n,输出 ∑u d(u,k)。
2. 状态定义
令 dp[k][u] 表示从 u 出发,最多使用 k 次传送,到达 1 的最短时间。
- 当 k = 0 时,不能使用传送,dp[0][u] = dist_tree(u, 1),可通过一次 DFS 求得。
3. 转移方程(关键转化)
考虑至少使用一次传送的情况。假设第一次传送发生在节点 x,通过传送边 (x, y) 到达 y,之后还剩 k-1 次传送机会。则
dp[k][u] = min( dp[0][u], min_{传送边 (x,y)} ( dist(u, x) + dp[k-1][y] ) )
为了高效计算,定义辅助数组 h[x]:
h[x] = min_{传送边 (x,y)} dp[k-1][y]
特别地,h[1] = 0(表示已经到达 1,无需再传送)。
那么转移简化为:
dp[k][u] = min_{x} ( dist(u, x) + h[x] )
即:每个节点 x 都有一个“源点代价” h[x],从 u 出发沿树边走到任意 x,总代价为树距 + h[x],取最小值。
4. 如何对每个 k 快速计算所有 dp[k][u]?
对于固定的 k,我们已经知道 dp[k-1][*],因此可以 O(m) 计算出所有 h[x](扫描所有传送边,取 min)。
现在问题转化为:给定一棵带边权的树,每个节点 x 有一个初始值 h[x](可能为 INF),求每个节点 u 到所有节点的 (dist(u, x) + h[x]) 的最小值。这是一个经典的树上多源最短路问题,可以通过换根 DP 在 O(n) 时间内解决,而无需 Dijkstra(因为图是树)。
5. 换根 DP 细节
我们定义:
- down[u]:在 u 的子树内(包括 u)所有 x 中,min( dist(u, x) + h[x] )
- up[u]:在 u 的子树外(即父节点方向)所有 x 中,min( dist(u, x) + h[x] )
最终 dp[k][u] = min(down[u], up[u])。
5.1 第一次 DFS(自底向上)
递归计算 down[u]:
down[u] = h[u] (选择自己)
对于每个儿子 v,边权 w:
down[u] = min(down[u], down[v] + w)
因为 down[v] 已经表示从 v 到其子树内某点的最优值,加上 w 即是从 u 到该点的距离。
5.2 第二次 DFS(自顶向下,换根)
我们需要为每个儿子 v 计算 up[v]。
对于父节点 u,首先求出所有儿子 v 的 (down[v] + w) 的最小值 min1 和次小值 min2,以及取得最小值的儿子 id。
当处理儿子 v 时,除 v 自身子树外,其他可能的外部源点来自:
- u 的父方向(up[u])
- u 自身(h[u])
- u 的其他儿子子树(即 min1 或 min2,取决于 v 是否为 id)
因此,从 u 到 v 的外部最优代价 fath = min( up[u], h[u], other ),其中 other = (v == id) ? min2 : min1。
然后 up[v] = w + fath,即从 v 走到 u,再沿外部最优路径。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll INF = 4e18;
int n, m;
vector<vector<pair<int, ll>>> g;
vector<pair<int, int>> tele;
vector<ll> pre_dp, dp;
vector<ll> h;
vector<ll> down, up;
void dist_dfs(int u, int p, ll dist) {
pre_dp[u] = dist;
for(auto &e : g[u]) {
int v = e.first; ll w = e.second;
if(v == p) continue;
dist_dfs(v, u, dist + w);
}
}
void dfs_down(int u, int p) {
down[u] = h[u];
for(auto&[v, w] : g[u]) {
if(v == p) continue;
dfs_down(v, u);
down[u] = min(down[u], down[v] + w);
}
}
void dfs_up(int u, int p) {
//找到次短路,用于维护最小值所在的子树
ll min1 = INF, min2 = INF;
int id = -1;
for(auto&[v, w] : g[u]) {
if(v == p) continue;
ll val = down[v] + w;
if(val < min1) {
min2 = min1; min1 = val; id = v;
}
else if(val < min2) min2 = val;
}
for(auto&[v, w] : g[u]) {
if(v == p) continue;
ll other = (v == id) ? min2 : min1;
ll fath = min({up[u], h[u], other});
//比较 祖父节点,自身传送源点,来自子树的距离
up[v] = w + fath;
dfs_up(v, u);
}
}
void solve() {
cin >> n >> m;
g.assign(n + 1, {});
tele.assign(m + 1, {});
for(int i = 1; i < n; i ++) {
int u, v; ll w; cin >> u >> v >> w;
g[u].push_back({v, w});
g[v].push_back({u, w});
}
for(int i = 0; i < m; i ++) cin >> tele[i].first >> tele[i].second;
dp.assign(n + 1, 0);
pre_dp.assign(n + 1, 0);
h.assign(n + 1, -INF);
down.assign(n + 1, -INF);
up.assign(n + 1, -INF);
//处理k=0的情况
dist_dfs(1, -1, 0);
ll ans = 0;
for(int u = 1; u <= n; u ++) ans += pre_dp[u];
cout << ans << '\n';
for(int k = 1; k <= n; k ++) {
fill(h.begin(), h.end(), INF); h[1] = 0;
//更新势函数
for(auto&[p, q] : tele) {
h[p] = min(h[p], pre_dp[q]);
h[q] = min(h[q], pre_dp[p]);
}
up[1] = INF;
dfs_down(1, -1);
dfs_up(1, -1);
ll sum = 0;
for(int u = 1; u <= n; u ++) {
dp[u] = min(down[u], up[u]);
sum += dp[u];
}
cout << sum << '\n';
pre_dp.swap(dp);
}
}
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
int _=1; //cin >> _;
while(_--) solve();
return 0;
}

浙公网安备 33010602011771号