P14080 [GESP202509 八级] 最小生成树

GESP8 冲刺课堂页:先建模,再压缩状态,最后读代码。

一、先回答问题

结论

非树边删掉不影响原 MST;树边删掉时要找覆盖它的最小非树边替换。

容易误入的路

每删一条边重新跑 Kruskal,m 到 1e5 会超时。

二、核心观察

建模与压缩

先求一棵 MST;每条非树边 (u,v) 可替换 MST 上 u-v 路径中的任意树边。

为什么不丢答案:非树边按权从小到大处理,第一次覆盖到某树边的非树边就是它最便宜替换。

MST 替换边 答案

三、互动演示

步骤1
关键状态 A-
关键状态 B-
答案/目标-
复杂度看表
课堂动作手推

四、自己选答案

问题 1

删除非树边答案为什么仍是 W?

问题 2

树边无替换边输出什么?

五、手推结果

边类型删除后
非树边原 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;
}