题解:洛谷 P6175 无向图的最小环问题
【题目来源】
【题目描述】
给定一张无向图,求图中一个至少包含 \(3\) 个点的环,环上的节点不重复,并且环上的边的长度之和最小。该问题称为无向图的最小环问题。在本题中,你需要输出最小的环的边权和。若无解,输出 No solution.。
【输入】
第一行两个正整数 \(n,m\) 表示点数和边数。
接下来 \(m\) 行,每行三个正整数 \(u,v,d\),表示节点 \(u,v\) 之间有一条长度为 \(d\) 的边。
【输出】
输出边权和最小的环的边权和。若无解,输出 No solution.。
【输入样例】
5 7
1 4 1
1 3 300
3 1 10
1 2 16
2 3 100
2 5 15
5 3 20
【输出样例】
61
【核心思想】
-
问题分析:给定一个无向图,需要找到一个至少包含 3 个顶点的简单环(顶点不重复),使得环上所有边的权值之和最小。若图中不存在这样的环,则输出
No solution.。本质是无向图最小环问题,可以利用 Floyd 算法在动态规划过程中检测环。 -
算法选择:
- Floyd 算法 + 环检测:在 Floyd 算法的递推过程中,当枚举到中间节点 \(k\) 时,
dist[i][j]表示当前已知的 \(i\) 到 \(j\) 的最短路径(该路径不经过编号大于 \(k-1\) 的节点,且中间节点编号均小于 \(k\))。此时,若存在边 \(i \leftrightarrow k\) 和 \(k \leftrightarrow j\),则 \(i \to \cdots \to j \to k \to i\) 可以构成一个环,其长度为dist[i][j] + g[i][k] + g[k][j]。因为dist[i][j]不经过 \(k\),所以该环是简单环。枚举所有 \(i < j < k\) 即可找到最小环。 - 初始化与重边处理:使用邻接矩阵存储图,对于重边取最小值。对角线置 0,其余置无穷大。
- Floyd 算法 + 环检测:在 Floyd 算法的递推过程中,当枚举到中间节点 \(k\) 时,
-
关键步骤:
- 读入数据:读取 \(n, m\),初始化邻接矩阵
g和距离矩阵dist为无穷大,dist[i][i] = 0。 - 处理重边:对于每条边 \((u, v, d)\),更新
g[u][v] = min(g[u][v], d),g[v][u]同理,并同步更新dist。 - Floyd 过程(含环检测):
- 外层循环 \(k\) 从 1 到 \(n\):
- 检测环:对于所有 \(1 \le i < k\) 且 \(1 \le j < k\)(确保 \(i, j, k\) 互不相同),若存在边 \(i \leftrightarrow k\) 和 \(k \leftrightarrow j\),则更新答案
ans = min(ans, dist[i][j] + g[i][k] + g[k][j])。 - 标准 Floyd 更新:使用 \(k\) 作为中间节点松弛所有点对:
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])。
- 检测环:对于所有 \(1 \le i < k\) 且 \(1 \le j < k\)(确保 \(i, j, k\) 互不相同),若存在边 \(i \leftrightarrow k\) 和 \(k \leftrightarrow j\),则更新答案
- 外层循环 \(k\) 从 1 到 \(n\):
- 输出:若
ans仍为无穷大,输出No solution.,否则输出ans。
- 读入数据:读取 \(n, m\),初始化邻接矩阵
-
时间/空间复杂度:
- 时间复杂度:\(O(n^3)\),三重循环枚举 \(k, i, j\)。
- 空间复杂度:\(O(n^2)\),存储两个邻接矩阵。
-
Floyd 最小环检测的核心思想:
- 环的构成:一个包含节点 \(k\) 的环可以分解为两条路径:从 \(i\) 到 \(j\) 的最短路径(不经过 \(k\))和两条直接边 \(i \leftrightarrow k\)、\(k \leftrightarrow j\)。在 Floyd 更新过程中,当 \(k\) 作为中间节点前,
dist[i][j]恰好是 \(i\) 到 \(j\) 且不经过 \(k\) 的最短路径,因此dist[i][j] + g[i][k] + g[k][j]构成一个经过 \(k\) 的简单环。 - 枚举顺序:枚举 \(k\) 时,所有 \(i, j\) 均小于 \(k\),保证了环中除 \(k\) 外的其他节点编号都小于 \(k\),从而避免重复计数,且能覆盖所有可能的环。
- 适用场景:适用于边权为正或非负的无向图(也可用于有向图,但本题为无向图),是求解全局最小环的经典算法。
- 环的构成:一个包含节点 \(k\) 的环可以分解为两条路径:从 \(i\) 到 \(j\) 的最短路径(不经过 \(k\))和两条直接边 \(i \leftrightarrow k\)、\(k \leftrightarrow j\)。在 Floyd 更新过程中,当 \(k\) 作为中间节点前,
【解题思路】

【算法标签】
普及 #Floyd
【代码详解】
#include <bits/stdc++.h>
using namespace std;
#define int long long // 使用长整型防止溢出
const int N = 105; // 最大节点数
const int INF = 0x3f3f3f3f; // 无穷大值
int n, m; // n:节点数, m:边数
int ans; // 存储最小环的长度
int dist[N][N]; // Floyd算法中的距离矩阵,记录当前最短路径
int g[N][N]; // 原始图的邻接矩阵,记录原始边权
/**
* Floyd算法求无向图的最小环
* 在Floyd算法执行过程中检测包含节点k的环
*/
void floyd()
{
// 外层循环:依次考虑每个节点k作为中间节点
for (int k = 1; k <= n; k++)
{
// 第一阶段:检测包含节点k的最小环
// 在更新距离矩阵之前,检查所有i->k->j的路径
for (int i = 1; i < k; i++)
{
for (int j = 1; j < k; j++)
{
// 确保i≠j,且i-k和k-j之间有边存在
if (i != j && g[i][k] < INF && g[k][j] < INF)
{
// 环的长度 = i->j的最短路径 + i->k的边 + k->j的边
// 此时dist[i][j]不包含节点k,因此形成环i->...->j->k->i
ans = min(ans, dist[i][j] + g[i][k] + g[k][j]);
}
}
}
// 第二阶段:标准的Floyd算法更新
// 使用节点k更新所有节点对之间的最短路径
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
// 状态转移:dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
// 无向图,保持对称性
dist[j][i] = dist[i][j];
}
}
}
}
signed main() // 使用signed代替int(因为定义了#define int long long)
{
// 输入节点数和边数
cin >> n >> m;
// 初始化距离矩阵和邻接矩阵
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
if (i != j)
{
// 初始时,不相邻的节点距离为无穷大
dist[i][j] = INF;
g[i][j] = INF;
}
}
}
// 输入所有边信息
while (m--)
{
int u, v, d;
cin >> u >> v >> d;
// 更新距离矩阵(处理重边,取最小值)
dist[u][v] = min(dist[u][v], d);
dist[v][u] = dist[u][v]; // 无向图,双向对称
// 更新原始邻接矩阵
g[u][v] = dist[u][v];
g[v][u] = dist[u][v];
}
// 初始化最小环为无穷大
ans = INF;
// 执行Floyd算法寻找最小环
floyd();
// 输出结果
if (ans == INF)
{
// 没有找到环
cout << "No solution.";
}
else
{
// 输出最小环的长度
cout << ans << endl;
}
return 0;
}
【运行结果】
5 7
1 4 1
1 3 300
3 1 10
1 2 16
2 3 100
2 5 15
5 3 20
61
浙公网安备 33010602011771号