题解:洛谷 P6175 无向图的最小环问题

【题目来源】

洛谷: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

【核心思想】

  1. 问题分析:给定一个无向图,需要找到一个至少包含 3 个顶点的简单环(顶点不重复),使得环上所有边的权值之和最小。若图中不存在这样的环,则输出 No solution.。本质是无向图最小环问题,可以利用 Floyd 算法在动态规划过程中检测环。

  2. 算法选择

    • 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,其余置无穷大。
  3. 关键步骤

    • 读入数据:读取 \(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])
    • 输出:若 ans 仍为无穷大,输出 No solution.,否则输出 ans
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n^3)\),三重循环枚举 \(k, i, j\)
    • 空间复杂度:\(O(n^2)\),存储两个邻接矩阵。
  5. 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\),从而避免重复计数,且能覆盖所有可能的环。
    • 适用场景:适用于边权为正或非负的无向图(也可用于有向图,但本题为无向图),是求解全局最小环的经典算法。

【解题思路】

【算法标签】

普及 #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
posted @ 2026-09-01 22:40  团爸讲算法  阅读(8)  评论(0)    收藏  举报