1. 程式人生 > 其它 >AcWing 344. 觀光之旅

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 <bits/stdc++.h>
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<int> 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;
}