题解:洛谷 P3366 【模板】最小生成树
【题目来源】
【题目描述】
如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出 orz。
【输入】
第一行包含两个整数 \(N,M\),表示该图共有 \(N\) 个结点和 \(M\) 条无向边。
接下来 \(M\) 行每行包含三个整数 \(X_i,Y_i,Z_i\),表示有一条长度为 \(Z_i\) 的无向边连接结点 \(X_i,Y_i\)。
【输出】
如果该图连通,则输出一个整数表示最小生成树的各边的长度之和。如果该图不连通则输出 orz。
【输入样例】
4 5
1 2 2
1 3 2
1 4 3
2 3 4
3 4 3
【输出样例】
7
【算法标签】
《洛谷 P3366 最小生成树》 #生成树#
【代码详解】
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
#define inf 1e9
using namespace std;
const int N = 5010; // 最大节点数
int n, m, a, b, c; // n:节点数, m:边数, a,b,c:临时变量
int ans; // 最小生成树的总权重
int cnt; // 已加入生成树的节点数
// 边结构体
struct edge
{
int v; // 目标节点
int w; // 边权重
};
vector<edge> e[N]; // 邻接表存储图
int d[N]; // 存储当前生成树到各节点的最小距离
int vis[N]; // 标记节点是否已加入最小生成树
// 使用大根堆模拟小根堆:存储{-距离, 节点}
priority_queue<pair<int, int>> q;
/**
* Prim算法(堆优化版):求解最小生成树
* @param s 起始节点
* @return true: 图连通,存在最小生成树;false: 图不连通
*/
bool prim(int s)
{
// 初始化距离数组为无穷大
for (int i = 0; i <= n; i++)
{
d[i] = inf;
}
// 起始节点距离设为0,并加入优先队列
d[s] = 0;
q.push({0, s});
// 当队列不为空时继续处理
while (q.size())
{
int u = q.top().second; // 获取当前距离最小的节点
q.pop();
// 如果节点已经处理过,跳过(延迟删除)
if (vis[u])
{
continue;
}
// 标记节点u已加入最小生成树
vis[u] = 1;
// 累加当前边的权重到总结果
ans += d[u];
// 已加入生成树的节点数加1
cnt++;
// 遍历节点u的所有邻接边
for (auto ed : e[u])
{
int v = ed.v; // 邻接节点
int w = ed.w; // 边权重
// 如果通过u到v的距离更小,更新距离
if (d[v] > w)
{
d[v] = w; // 更新到v的最小距离
// 将新距离加入优先队列(使用负数模拟小根堆)
q.push({-d[v], v});
}
}
}
// 检查是否所有节点都加入了生成树(图是否连通)
return cnt == n;
}
int main()
{
// 输入节点数和边数
cin >> n >> m;
// 输入所有边信息,构建无向图
for (int i = 0; i < m; i++)
{
cin >> a >> b >> c;
// 无向图,双向添加边
e[a].push_back({b, c});
e[b].push_back({a, c});
}
// 从节点1开始执行Prim算法
if (!prim(1))
{
// 图不连通,无法生成最小生成树
puts("orz");
}
else
{
// 输出最小生成树的总权重
printf("%d\n", ans);
}
return 0;
}
// 使用acwing模板二刷
#include <bits/stdc++.h>
using namespace std;
const int N = 5005; // 最大节点数
const int INF = 0x3f3f3f3f; // 无穷大值
int n; // 节点数量
int m; // 边数量
int g[N][N]; // 邻接矩阵存储图
int dist[N]; // 存储当前生成树到各节点的最小距离
bool st[N]; // 标记节点是否已加入最小生成树
/**
* Prim算法:求解无向连通图的最小生成树
* @return 最小生成树的总权重,INF表示图不连通
*/
int prim()
{
// 初始化距离数组为无穷大
memset(dist, 0x3f, sizeof(dist));
int res = 0; // 存储最小生成树的总权重
// 循环n次,每次加入一个节点到生成树
for (int i = 0; i < n; i++)
{
int t = -1; // 用于记录当前距离最小的节点
// 在所有未加入生成树的节点中,找到距离最小的节点
for (int j = 1; j <= n; j++)
{
if (!st[j] && (t == -1 || dist[t] > dist[j]))
{
t = j;
}
}
// 如果不是第一个节点(i>0)且最小距离为无穷大,说明图不连通
if (i && dist[t] == INF)
{
return INF; // 图不连通,无法生成最小生成树
}
// 如果不是第一个节点,将当前边的权重加入总结果
if (i)
{
res += dist[t];
}
// 用新加入的节点t更新其他节点到生成树的最小距离
for (int j = 1; j <= n; j++)
{
dist[j] = min(dist[j], g[t][j]);
}
// 标记节点t已加入最小生成树
st[t] = true;
}
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);
// 如果数据中没有重边,可以使用下面这行:
// g[a][b] = g[b][a] = c;
}
// 调用Prim算法计算最小生成树
int t = prim();
// 输出结果
if (t == INF)
{
// 图不连通,无法生成最小生成树
puts("orz");
}
else
{
// 输出最小生成树的总权重
printf("%d\n", t);
}
return 0;
}
【运行结果】
4 5
1 2 2
1 3 2
1 4 3
2 3 4
3 4 3
7
浙公网安备 33010602011771号