【例4-11】最短网络(agrinet)

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

完全图邻接矩阵求最小生成树:Prim 数组版每轮选距离树最近的点入树并用其所在行松弛,O(n²) 与读入同阶。

OJ: roj

题目 ID: 1350

难度:普及-

标签:最小生成树图论贪心python

创建: 2026-09-30 06:40

更新: 2026-10-05 11:23

题目描述

农民约翰当选镇长后承诺给所有农场连上互联网:已有一条高速线路接入了他的农场,他想用最短的光纤把其余农场都连起来。给出农场个数 N(3≤N≤100)和一个 N×N 矩阵,第 i 行第 j 列表示农场 i 与 j 之间的连接费用(对角线为 0,费用不超过 100000),矩阵实际按 80 字符折行、当作空白分隔的数字流读入。求连接所有农场的光纤最小总长度。

样例输入:4、0 4 9 21、4 0 8 17、9 8 0 16、21 17 16 0

样例输出:28

思路

「连通所有农场且总长最短」就是最小生成树的字面定义,输入又是稠密图的邻接矩阵,所以用与读入同阶的 O(n²) Prim 数组版。维护 dis[j] 为树外点 j 到当前树的最短边权,每轮把距离树最近的树外点拉入树,并用它所在行对其余树外点做一遍取 min 松弛。

参考代码

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-10-08 20:30
# update_at: 2026-10-08 20:30

import sys


def solve() -> None:
    data = iter(map(int, sys.stdin.buffer.read().split()))
    n = next(data)
    g = [[next(data) for _ in range(n)] for _ in range(n)]  # n*n 距离矩阵

    INF = float('inf')
    dis = g[0][:]        # dis[j]:树外点 j 到当前树的最短边长,初始树只有农场 0
    dis[0] = INF         # 农场 0 已在树里,不参与选取
    in_tree = [True] + [False] * (n - 1)  # 点是否已并入生成树
    answer = 0
    for _ in range(n - 1):  # 每轮并入一个点、加一条边,共 n-1 轮
        k = min(range(n), key=dis.__getitem__)  # 距离树最近的树外点 k
        answer += dis[k]
        in_tree[k] = True
        # 用 k 的出边松弛:树外的点取 min,树内的点一律置回 INF 不再参与
        dis = [INF if t else min(d, gk) for d, gk, t in zip(dis, g[k], in_tree)]

    print(answer)


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-05 11:23
 * update_at: 2026-10-05 11:23
 */
#include <cstdio>

typedef long long ll;

const int MAXN = 105;
const ll INF = 0x3F3F3F3F3F3F3F3F;

int n;             // 农场个数
ll g[MAXN][MAXN];  // g[i][j] 表示农场 i 与 j 之间的距离(邻接矩阵)
ll dis[MAXN];      // dis[j] 表示树外点 j 到当前生成树的最短边权;入树后为 INF
bool in_tree[MAXN];// in_tree[j] 表示点 j 是否已进入生成树

int main() {
    scanf("%d", &n);
    // 矩阵按 80 字符折行,逻辑行与物理行不一致,用 token 流读入
    for (int i = 0; i < n; ++i)
        for (int j = 0; j < n; ++j)
            scanf("%lld", &g[i][j]);

    // Prim:以 0 号农场为起点,初始只有它在树里
    for (int j = 0; j < n; ++j) dis[j] = g[0][j];
    dis[0] = INF;      // 已入树的点恒为 INF,否则第一轮会重复选中起点
    in_tree[0] = true;

    ll ans = 0;        // 最小生成树的边权总和
    for (int round = 1; round < n; ++round) {
        // 选距离树最近的树外点 k 拉进树(cut 性质保证贪心正确)
        int k = -1;
        for (int j = 0; j < n; ++j)
            if (!in_tree[j] && (k == -1 || dis[j] < dis[k]))
                k = j;
        ans += dis[k];
        in_tree[k] = true;
        dis[k] = INF;

        // 用 k 所在行松弛其余树外点的距离;对角线 g[k][k]=0 不会影响已入树点
        for (int j = 0; j < n; ++j)
            if (!in_tree[j] && g[k][j] < dis[j])
                dis[j] = g[k][j];
    }

    printf("%lld\n", ans);
    return 0;
}