# 前置文章

Floyd 算法简单入门

# AcWing 344. 观光之旅

给定一张无向图,求图中一个至少包含 3 个点的环,环上的节点不重复,并且环上的边的长度之和最小。

该问题称为无向图的最小环问题。

你需要输出最小环的方案,若最小环不唯一,输出任意一个均可。

输入格式
第一行包含两个整数 N 和 M,表示无向图有 N 个点,M 条边。

接下来 M 行,每行包含三个整数 u,v,l,表示点 u 和点 v 之间有一条边,边长为 l。

输出格式
输出占一行,包含最小环的所有节点(按顺序输出),如果不存在则输出 No solution.。

数据范围

1≤N≤100,
1≤M≤10000,
1≤l<500

输入样例

5 7
1 4 1
1 3 300
3 1 10
1 2 16
2 3 100
2 5 15
5 3 20

输出样例

1 3 5 2

# 题目描述

给出一张无向图,问 图中所有 至少包含三个点的环中,环上的边的 长度总和 最小 是多少

# 题目分析

本题如果是求 两个点中的环长度最小非常简单,两个环其实就是边,因为是无向图,所以就是求最小的边,但是本题要求了至少三个边组成的环,问题就变得麻烦了起来。

# 关于 “环上的节点不重复”

题目中给出了一个条件 “环上的节点不重复”,说明,每个环中任何一个节点都只存在一次。但是这个条件,我们仔细思考一下,会发现即便环上的节点是重复的对最终结果也没有影响,因为根据数据范围,所有的边的权重都是大于一的,那么,我们去除重复的边一定可以使整个环的权重变小。

如下图,虽然绿色边使那个点被经过了两次,但是因为边的长度一定大于一,所以去掉边后依旧是一个环,并且环的权重更小。

# 动态规划的思路

因为 floyd 的思路本质上是动态规划,所以我们也按照动态规划的思路从集合的角度来分析本题

首先,我们要想一种划分方式,将所有的环进行分类。一般的动态规划中,我们经常以最后一个状态是由哪个状态转移而来进行划分。

在本题中,我们继承这种思路,把所有的环,根据编号最大的点进行划分集合

注意这种划分方式的好处是:

  1. 可以不重不漏的包含所有的环;
  2. 划分好后,我们求出每一个集合中的最小值,然后再从每一个集合的最小值中,找出最终的最小值,就是结果。

那么,每一集合中的最小值怎么求呢?

可以从 floyd 的求解过程中找到答案。

for (int k = 0; k < n; k ++ )
    // 此处已经求出了只经过 (1~k-1) 这些点的 最短路径 d [i][j]
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < n; j ++ )
            d[i][j] = min(d[i][j], d[i][k] + d[k][j]);

我们知道 floyd 算法的最大作用就是可以求出有边数限制的最短路。其中边数限制指的就是以上代码中的第一重循环 k ,也就是说当循环到 k 的时候,就已经知道了,所有从 i 到 j(d [i,j]),只经过 1~k-1 这些点的最短路径

所以,我们在这个位置(上面代码注释的位置)就可以求出第 k 类(也就是编号最大为 k 的集合中 的环 的最小权重)。

那怎么求呢?我们可以发现,这个集合(k 为环中最大点的编号,下面简称集合 k)中的环,全部是如下形式:

根据图中的分析,我们知道了集合 k 中的环,全部是由三部分组成:

  1. i 到 k 的距离是固定的,记作 w[i][k]
  2. j 到 k 的距离也是固定的,记作 w[j][k]
  3. 所有 i 到 j,且经过的 最大点编号 小于等于 k-1 的最短距离,在第一重循环到 k 的时候已经知道了。记作 d[i][j]

那么最终集合 k 的最小值就是:枚举 i 和 j,求 min({d[i][j]}) + w[i][k] + w[j][k]

那么最终所有集合最小值就是:枚举所有的 集合k ,也即是 集合1~n ,然后求他们的一个最小值就行了。

# Code

本题在实现上还有一个难点,既:最后题目要求输出包含最小环的所有节点(按顺序输出)

因此,我们还需要存储一下节点的转移路线。

考虑状态转移的部分: d[i][j]=d[i][k]+d[k][j] ,其中边 {i,j}k 分成两部分,当前的 d[i][j] 由中间点 k 转移而来,因此在每次转移的时候记录这个点 k,记作 pos[i][j]=k

有了 pos 数组,求环的路径的时候就可以通过递归来遍历出整条环上的路径。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110, INF = 0x3f3f3f3f;
int n, m;
// 数组含义见分析
int w[N][N], d[N][N];
int pos[N][N];
//path 表示当前最小环的路径 cnt 表示对应路径的点数量
int path[N], cnt;
void get_path(int i, int j) {
    // 如果 ij 没被更新过 说明没有中间点
    // 它俩就组成一条边
    if (pos[i][j] == 0) return ;
    int k = pos[i][j]; // 取出更新它的点 k
    get_path(i, k); // 先从 i 走到它俩的中间点 k
    path[cnt ++ ] = k; // 把 k 加上
    get_path(k, j); // 再从 k 走到 j
}
int main() {
    cin >> n >> m;
    // 初始化两点间的距离
    memset(w, 0x3f, sizeof w);
    for (int i = 1; i <= n; i ++ ) w[i][i] = 0;
    while (m -- ) {
        int a, b, c;
        cin >> a >> b >> c;
        // 无向图 建立双向边 以及 重边的话取最小值
        w[a][b] = w[b][a] = min(w[a][b], c);
    }
    int res = INF;
    memcpy(d, w, sizeof d);
    //floyd 算法
    for (int k = 1; k <= n; k ++ ) {
        // 在这个位置 枚举每个集合的最小值 并更新最终答案
        // 注意 i 和 j 的范围都是 1~k-1
        // 但是因为 是双向图 w [i][j] == w [j][i]
        // 因此 j 枚举一半就行 所以我们选择从 i+1 枚举 j
        for (int i = 1; i < k; i ++ ) {
            for (int j = i + 1; j < k; j ++ ) {
                // 判断当前环 是否能更新结果 不转 LL 会爆 int
                if ((long long)d[i][j] + w[i][k] + w[k][j] < res) {
                    res = d[i][j] + w[i][k] + w[k][j];
                    // 当前环是最小环 那把当前环的路线 更新到 path 数组
                    cnt = 0;
                    path[cnt ++ ] = k; // 先放点 k
                    path[cnt ++ ] = i; // 从 k 走到 i 再放点 i
                    get_path(i, j); // 路径 i 到 j 有很多点 递归放 i~j 最短距离的所有点
                    path[cnt ++ ] = j; // 最后放 j
                }
            }
        }
        for (int i = 1; i <= n; i ++ ) {
            for (int j = 1; j <= n; j ++ ) {
                if (d[i][j] > d[i][k] + d[k][j]) {
                    d[i][j] = d[i][k] + d[k][j];
                    // 记录当前 {i,j} 最小距离是由哪个 k 转换而来的
                    pos[i][j] = k;
                }
            }
        }
    }
    if (res == INF) puts("No solution.");
    else {
        for (int i = 0; i < cnt; i ++ ) cout << path[i] << ' ';
    }
}

END

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

A Cat Without Sugar 微信支付

微信支付

A Cat Without Sugar 支付宝

支付宝