非树边删掉不影响原 MST;树边删掉时要找覆盖它的最小非树边替换。
P14080 [GESP202509 八级] 最小生成树
GESP8 冲刺课堂页:先建模,再压缩状态,最后读代码。
一、先回答问题
每删一条边重新跑 Kruskal,m 到 1e5 会超时。
二、核心观察
先求一棵 MST;每条非树边 (u,v) 可替换 MST 上 u-v 路径中的任意树边。
为什么不丢答案:非树边按权从小到大处理,第一次覆盖到某树边的非树边就是它最便宜替换。
MST
替换边
答案
三、互动演示
步骤1
关键状态 A-
关键状态 B-
答案/目标-
复杂度看表
课堂动作手推
点击下一步开始。
四、自己选答案
删除非树边答案为什么仍是 W?
先判断,再点按钮。
树边无替换边输出什么?
先判断,再点按钮。
五、手推结果
| 边类型 | 删除后 |
|---|---|
| 非树边 | 原 MST 仍可用 |
| 树边有替换 | W-w+best |
| 树边无替换 | -1 |
课堂要求:每题先说清对象、关系、保留的信息、压缩掉的信息,再写代码。
六、C++14 参考代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 100005;
const int LOGN = 18;
struct Edge {
int u, v, w, id;
bool is_tree;
};
bool cmp_w(const Edge& a, const Edge& b) {
return a.w < b.w;
}
vector<pair<int, int>> adj[MAXN]; // node, edge_idx
int f[MAXN][LOGN], dep[MAXN], p_edge[MAXN]; // p_edge[u] stores the edge id from u to its parent
int dsu[MAXN];
int find_dsu(int i) {
return dsu[i] == i ? i : dsu[i] = find_dsu(dsu[i]);
}
void dfs(int u, int p, int d, int e_id) {
dep[u] = d;
f[u][0] = p;
p_edge[u] = e_id;
for (int i = 1; i < LOGN; i++) {
f[u][i] = f[f[u][i - 1]][i - 1];
}
for (auto& edge : adj[u]) {
if (edge.first != p) {
dfs(edge.first, u, d + 1, edge.second);
}
}
}
int get_lca(int u, int v) {
if (dep[u] < dep[v]) swap(u, v);
for (int i = LOGN - 1; i >= 0; i--) {
if (dep[f[u][i]] >= dep[v]) u = f[u][i];
}
if (u == v) return u;
for (int i = LOGN - 1; i >= 0; i--) {
if (f[u][i] != f[v][i]) {
u = f[u][i];
v = f[v][i];
}
}
return f[u][0];
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
cin >> n >> m;
vector<Edge> edges(m);
for (int i = 0; i < m; i++) {
cin >> edges[i].u >> edges[i].v >> edges[i].w;
edges[i].id = i;
edges[i].is_tree = false;
}
vector<Edge> sorted_edges = edges;
sort(sorted_edges.begin(), sorted_edges.end(), cmp_w);
// Kruskal's
vector<int> mst_dsu(n + 1);
iota(mst_dsu.begin(), mst_dsu.end(), 0);
auto find_mst = [&](auto self, int i) -> int {
return mst_dsu[i] == i ? i : mst_dsu[i] = self(self, mst_dsu[i]);
};
ll mst_weight = 0;
int edges_count = 0;
vector<int> is_tree_edge(m, 0);
for (int i = 0; i < m; i++) {
int root_u = find_mst(find_mst, sorted_edges[i].u);
int root_v = find_mst(find_mst, sorted_edges[i].v);
if (root_u != root_v) {
mst_dsu[root_u] = root_v;
mst_weight += sorted_edges[i].w;
edges_count++;
is_tree_edge[sorted_edges[i].id] = 1;
adj[sorted_edges[i].u].push_back({sorted_edges[i].v, sorted_edges[i].id});
adj[sorted_edges[i].v].push_back({sorted_edges[i].u, sorted_edges[i].id});
}
}
if (edges_count < n - 1) {
for (int i = 0; i < m; i++) cout << -1 << "\n";
return 0;
}
dfs(1, 1, 0, -1);
vector<int> best_repl(m, -1);
iota(dsu, dsu + n + 1, 0);
for (int i = 0; i < m; i++) {
if (is_tree_edge[sorted_edges[i].id]) continue;
int u = sorted_edges[i].u;
int v = sorted_edges[i].v;
int w = sorted_edges[i].w;
int lca = get_lca(u, v);
u = find_dsu(u);
while (dep[u] > dep[lca]) {
best_repl[p_edge[u]] = w;
dsu[u] = find_dsu(f[u][0]);
u = dsu[u];
}
v = find_dsu(v);
while (dep[v] > dep[lca]) {
best_repl[p_edge[v]] = w;
dsu[v] = find_dsu(f[v][0]);
v = dsu[v];
}
}
for (int i = 0; i < m; i++) {
if (!is_tree_edge[i]) {
cout << mst_weight << "\n";
} else {
if (best_repl[i] == -1) cout << -1 << "\n";
else cout << mst_weight - edges[i].w + best_repl[i] << "\n";
}
}
return 0;
}