usaco-3.1.1 最短网络

GitHub跳转原题关系图返回列表

基于稠密完全图邻接矩阵的 Prim 算法求最小生成树

OJ: roj

题目 ID: 2040

难度:普及-

标签:图论最小生成树

创建: 2026-10-01 04:29

更新: 2026-10-06 10:10

题目描述

约翰有 nn 个农场,要铺设最短光纤把它们都连通。输入给出 nn 及一个 n×nn \times n 的邻接矩阵,矩阵元素表示两农场之间的距离(对称,对角线为 0)。求连接所有农场的最小光纤总长度。

思路

这是一道稠密图最小生成树模板题。n⩽100n \leqslant 100,直接在邻接矩阵上跑 O(n2)O(n^2) 的 Prim 算法即可:每次把到当前树距离最小的农场加入树,再用它到其余农场的距离更新最小距离数组,最后累加答案。

参考代码

Python 版:

python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-04-18 10:00
# update_at: 2026-04-18 10:00

import sys

INF = 10**18


def prim_mst(n: int, adj: list[list[int]]) -> int:
    """计算稠密图的最小生成树边权和。"""
    min_cost = [INF] * n
    min_cost[0] = 0
    visited = [False] * n

    for _ in range(n):
        # 挑选当前未加入树且到树距离最小的农场
        u = min((v for v in range(n) if not visited[v]), key=lambda v: min_cost[v])
        visited[u] = True
        for v in range(n):
            if not visited[v] and adj[u][v] < min_cost[v]:
                min_cost[v] = adj[u][v]

    return sum(min_cost)


def solve() -> None:
    data = iter(map(int, sys.stdin.buffer.read().split()))
    n = next(data)
    adj = [[next(data) for _ in range(n)] for _ in range(n)]
    print(prim_mst(n, adj))


if __name__ == "__main__":
    solve()

C++ 版(同一算法):

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-10-06 10:09
 * update_at: 2026-10-06 10:09
 */

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

const int MAXN = 105;
const ll INF = (ll)1e18;

ll adj[MAXN][MAXN];     // 邻接矩阵,adj[i][j] 表示农场 i 到农场 j 的距离
ll min_cost[MAXN];      // 未加入树的农场到当前树的最小边权
bool in_tree[MAXN];     // 标记农场是否已加入最小生成树

// 在稠密完全图上用 O(n^2) Prim 算法求最小生成树边权和
ll prim_mst(int n) {
    for (int i = 1; i <= n; ++i) {
        min_cost[i] = INF;
        in_tree[i] = false;
    }
    min_cost[1] = 0; // 从农场 1 开始构建生成树

    ll total = 0;
    for (int round = 1; round <= n; ++round) {
        // 在未加入树的农场中选出到树距离最小的点 u
        int u = -1;
        for (int i = 1; i <= n; ++i) {
            if (!in_tree[i] && (u == -1 || min_cost[i] < min_cost[u])) {
                u = i;
            }
        }

        in_tree[u] = true;
        total += min_cost[u];

        // 用 u 到其它未加入树农场的边松弛最小距离
        for (int v = 1; v <= n; ++v) {
            if (!in_tree[v] && adj[u][v] < min_cost[v]) {
                min_cost[v] = adj[u][v];
            }
        }
    }
    return total;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n;
    if (!(cin >> n)) return 0;
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= n; ++j) {
            cin >> adj[i][j];
        }
    }

    cout << prim_mst(n) << "\n";
    return 0;
}