AcWing 344. 观光之旅


题目传送门

一、Floyd求最小环

(\(Floyd\)) \(O(n^3)\)

设环的形式是:\(i<->k<->j\) , \(i<–>j\) (i\(,j,k\)不同)

\(floyd\)是典型的插点算法,每次插入点\(k\),为此,在点\(k\)被[插入前]可计算\(i->j->k\)这个环
即此时中间节点为:\(1~k-1\),即我们已经算出了任意\(i<->j\)的最短道路,中间经过的节点可以为 \((1,2,3,…,k-1)\)
我们只需枚举所有以\(k\)为环中最大节点的环即可。

\(pos[i][j]:i~j\)的最短路中经过的点是\(k\)(即由这个状态转移过来),且这个\(k\)是此路径中编号最大的点(除\(i,j\))//根据\(Floyd\)算法实质决定
这条道路存在以下两条性质
1.在\(i~j\)的最短道路中,一定没有环(显然)
2.设\(i,j\)之间的最短道路经过点\(k\)(不同于\(i,j\)),则\(i~k , k~j\)之间必然没有交集

二、实现代码

#include 
using namespace std;
// 使用Floyd求最小环的经典题
const int N = 110; //稠密图,最多100个顶点,10000条边
const int INF = 0x3f3f3f3f;
typedef long long LL;
LL res = INF;     //最小环的长度,因为是多条路径的和,可能大于INT_MAX
int g[N][N];      //邻接矩阵,存储任意两点间的 "直接距离"
int d[N][N];      //邻接矩阵,存储任意两点间的 "最短距离"
int pos[N][N];    //记录i~j的最短距离是由k做为中间点获取到的
vector path; //最小环的组成点有哪些
int n, m;         // n行m列的二维矩阵

// i->j之间的路,输出i到j之间不包括i和j的道路
void dfs(int i, int j) {
    int k = pos[i][j];  // 中间转移点
    if (k == 0) return; // 没有转移点返回
    dfs(i, k);          // 递归计算i~k之间的路径
    path.push_back(k);  // k
    dfs(k, j);          // 递归计算k~j之间的路径
}

//获取 i->k->j-->i的环路径
void get_path(int i, int j, int k) {
    path.clear();
    path.push_back(k); //边界
    path.push_back(i);
    dfs(i, j); // k->i-->j->k
    path.push_back(j);
}

int main() {
    cin >> n >> m; // n个点,m条边

    memset(g, 0x3f, sizeof g);               //任意两点间距离正无穷
    for (int i = 0; i < n; i++) g[i][i] = 0; //自己和自己是距离为0的

    int a, b, c;
    while (m--) {
        cin >> a >> b >> c;
        g[a][b] = g[b][a] = min(g[a][b], c); //保留最短边
    }

    memcpy(d, g, sizeof d); //原图

    for (int k = 1; k <= n; k++) {
        //至少包含三个点的环所经过的点的最大编号是k
        //当枚举到k时,其实现在g数组中保存的是前k-1条边时的多原最短路径
        //如果引入k点,则可能的环路径长度为g[i][j] + d[i][k] + d[k][j]
        // d:直接路径
        // g:最短路径长度(经过Floyd计算过的最短路径)
        for (int i = 1; i < k; i++) //至少包含三个点,i,j,k不重合
            for (int j = i + 1; j < k; j++)
                if (res > (LL)g[i][j] + d[i][k] + d[k][j]) { //最小环
                    res = g[i][j] + d[i][k] + d[k][j];
                    get_path(i, j, k); // i->k->-j-->i为最小环,需要记录一下路径
                }
        //原始版本Floyd
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                if (g[i][j] > g[i][k] + g[k][j]) {
                    g[i][j] = g[i][k] + g[k][j];
                    pos[i][j] = k; //这里比原始版本Floyd多了一步记录任意两点是通过k进行转移的
                }
    }
    //如果没有被修改过,则返回不存在最小环
    if (res == INF)
        cout << "No solution." << endl;
    else {
        //输出路径
        for (auto x : path) cout << x << ' ';
        cout << endl;
    }
    return 0;
}