把费用矩阵看成完全带权图,问题即求最小生成树;用朴素 Prim 每轮接入 dist 最小的点,并用它那一行松弛其余未接入点,时间 O(n^2)。
OJ: roj
题目 ID: 1349
难度:普及-
标签:图论最小生成树Prim贪心python
创建: 2026-09-30 06:34
更新: 2026-10-05 11:15
题目描述
学校有 3 / 0 1 2 / 1 0 1 / 2 1 0,输出 2(连接 1-2 与 2-3,费用
思路
把费用矩阵看成一张完全无向带权图,「所有计算机连通且总费用最小」就是求它的最小生成树。用朴素 Prim:维护 dist[v] 表示已接入点集连到 v 的最便宜边权,每轮取未接入点中 dist 最小的接入并累加费用,再用它那一行松弛其余未接入点,
参考代码
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-09-30 06:34
# update_at: 2026-10-04 11:16
import sys
INF = 10**18 # dist 的初值:大于任何合法费用之和,表示暂时没有可用的接入边
def prim(cost: list[list[int]]) -> int:
"""返回费用矩阵所刻画完全图的最小生成树权值和,每轮接入最便宜的可接入点。"""
n = len(cost)
dist = [INF] * n # dist[v] = 已接入点集连到 v 的最便宜一条边的费用
dist[0] = 0 # 任选顶点 0 当根,接入自己不要钱
unvisited = set(range(n))
total = 0
while unvisited:
u = min(unvisited, key=dist.__getitem__) # 最便宜的可接入点:连上它的那条边必须进生成树
unvisited.remove(u)
total += dist[u]
for v in unvisited: # 接入 u 后,各未接入点的最便宜接入边可能改成走 u
dist[v] = min(dist[v], cost[u][v]) # 只遍历未接入点,否则 cost[u][u] 的 0 会覆写 dist[u]
return total
def solve() -> None:
data = iter(map(int, sys.stdin.buffer.read().split()))
n = next(data) # 题面的计算机台数
# 第 i 行是顶点 i 到各点的费用,按行顺序消费 n×n 个整数
cost = [[next(data) for _ in range(n)] for _ in range(n)]
print(prim(cost))
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:15
* update_at: 2026-10-05 11:15
*/
#include <iostream>
using namespace std;
typedef long long ll;
const int MAXN = 105; // n <= 100,留几个余量
const ll INF = 1e18; // dist 的初值,大于任何合法费用之和
ll cost[MAXN][MAXN]; // cost[x][y]:直接连接第 x、y 台计算机的费用
ll dist[MAXN]; // dist[v]:已接入点集连到 v 的最便宜一条边的费用
bool used[MAXN]; // used[v]:第 v 台计算机是否已经接入
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
ll n; // 计算机台数
cin >> n;
for (ll i = 1; i <= n; i++) {
for (ll j = 1; j <= n; j++) {
cin >> cost[i][j];
}
}
// 朴素 Prim:任选 1 号点当起点,接入自己不要钱
for (ll i = 1; i <= n; i++) {
dist[i] = INF;
used[i] = false;
}
dist[1] = 0;
ll ans = 0; // 最小生成树的权值和
for (ll round = 1; round <= n; round++) {
// 在未接入点里找 dist 最小的,连它的那条边就是横跨切割的最便宜边
ll u = 0;
for (ll v = 1; v <= n; v++) {
if (!used[v] && (u == 0 || dist[v] < dist[u])) {
u = v;
}
}
used[u] = true;
ans += dist[u];
// 接入 u 后各未接入点可以改走 u 中转;只松弛未接入点,
// 否则 cost[u][u] = 0 会把自己那行覆盖成 0
for (ll v = 1; v <= n; v++) {
if (!used[v] && cost[u][v] < dist[v]) {
dist[v] = cost[u][v];
}
}
}
cout << ans << '\n';
return 0;
}