题目地址:
https://www.acwing.com/problem/content/860/
给定一个 n n n个点 m m m条边的无向图,图中可能存在重边和自环,边权可能为负数。求最小生成树的树边权重之和,如果最小生成树不存在则输出impossible。
数据范围:
1 ≤ n ≤ 500 1\le n\le 500 1≤n≤500
1 ≤ m ≤ 1 0 5 1\le m\le 10^5 1≤m≤105
1 ≤ ∣ e ∣ ≤ 10000 1\le |e|\le 10000 1≤∣e∣≤10000
e e e是边长
可以用Prim算法。这个图是稠密图,所以可以用朴素版Prim算法。这个算法的思路是这样的。维护一个集合 S S S和一个数组 d d d, S S S是已经挑过的点,它们的最小生成树已经求出,而 d d d维护的是每个点到 S S S的最短距离,初始化为 ∞ \infty ∞(这个最短距离的定义是 d [ v ] = min w ∈ S { v , w } d[v]=\min_{w\in S}\{v,w\} d[v]=minw∈S{ v,w},对于 v ∈ S v\in S v∈S的情况无定义),每次都挑一个 d d d最小的点,将其加入 S S S(加入了 S S S的同时,就把它与 S S S的距离所对应的 S S S的那个点所连的边也加入了最小生成树),并以它来更新所有还没加入 S S S的点的最短距离。接着重复上述过程,每次循环都加一个点进 S S S,当所有点都加进去的时候,就得出了最小生成树。第一个加入 S S S的点没有限制,可以随便挑。但是如果在第 k > 1 k>1 k>1次挑的点,发现它与 S S S的最短距离是 ∞ \infty ∞,那说明这个点与 S S S里的点不连通,则最小生成树不存在,输出impossible。这里还需要注意一个问题。一定要先把点加入 S S S并累加最小生成树总权重,再用这个点更新最短距离。原因是,如果这个点自己到自己有个负环的话,更新完距离之后,它的 d d d值会把负环也给累加进去,这就不对了。代码如下:
#include <iostream>
#include <cstring>
using namespace std;
const int N = 510, INF = 0x3f3f3f3f;
int n, m;
int g[N][N];
int dist[N];
bool st[N];
int prim() {
memset(dist, 0x3f, sizeof dist);
int res = 0;
for (int i = 0; i < n; i++) {
int t = -1;
// 找到还没入S的点中与S距离最短的点
for (int j = 1; j <= n; j++)
if (!st[j] && (t == -1 || dist[t] > dist[j])) t = j;
// 如果不是第一次挑的点,并且所有不在S中的点与S距离都是正无穷,
// 说明原图不连通,则不存在最小生成树
if (i && dist[t] == INF) return INF;
// 把点t加入S,并累加边权
if (i) res += dist[t];
st[t] = true;
for (int j = 1; j <= n; j++)
if (!st[j]) dist[j] = min(dist[j], g[t][j]);
}
return res;
}
int main() {
scanf("%d%d", &n, &m);
memset(g, 0x3f, sizeof g);
while (m--) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
// 平行边只选边权最小的那个边
g[a][b] = g[b][a] = min(g[a][b], c);
}
int t = prim();
if (t == INF) cout << "impossible" << endl;
else cout << t << endl;
return 0;
}
时空复杂度 O ( n 2 ) O(n^2) O(n2)。
也可以用堆优化版的Prim算法来做。思路和朴素版一样,只是在挑点的时候,可以用最小堆来每次挑出一个距离 S S S最近的点。代码如下:
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
typedef pair<int, int> PII;
const int N = 100010, M = 2 * N, INF = 0x3f3f3f3f;
int n, m;
int h[N], w[M], e[M], ne[M], idx;
int cnt;
int dist[N];
bool st[N];
void add(int a, int b, int c) {
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
int prim() {
memset(dist, 0x3f, sizeof dist);
priority_queue<PII, vector<PII>, greater<PII> > heap;
heap.push({
INF, 1});
dist[1] = 0;
int res = 0;
while (!heap.empty()) {
auto t = heap.top();
heap.pop();
int v = t.second, c = t.first;
if (st[v]) continue;
if (v != 1) {
res += c;
cnt++;
if (cnt == n - 1) break;
}
st[v] = true;
for (int i = h[v]; i != -1; i = ne[i]) {
int j = e[i], c = w[i];
if (!st[j] && dist[j] > c) {
dist[j] = c;
heap.push({
c, j});
}
}
}
return cnt < n - 1 ? INF : res;
}
int main() {
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
while (m--) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
if (a == b) continue;
add(a, b, c), add(b, a, c);
}
int t = prim();
if (t == INF) cout << "impossible" << endl;
else cout << t << endl;
return 0;
}
时间复杂度 O ( m log n ) O(m\log n) O(mlogn),空间 O ( n + m ) O(n+m) O(n+m)。