1. 程式人生 > 實用技巧 >【題解】Acwing 91 最短Hamilton路徑

【題解】Acwing 91 最短Hamilton路徑

最短Hamilton路徑

題目傳送門

給定一張 n 個點的帶權無向圖,點從 0~n-1 標號,求起點 0 到終點 n-1 的最短Hamilton路徑。
Hamilton路徑的定義是從 0 到 n-1 不重不漏地經過每個點恰好一次。

輸入格式

第一行輸入整數n。

接下來n行每行n個整數,其中第i行第j個整數表示點i到j的距離(記為a[i,j])。

對於任意的x,y,z,資料保證 a[x,x]=0,a[x,y]=a[y,x] 並且 a[x,y]+a[y,z]>=a[x,z]。

輸出格式

輸出一個整數,表示最短Hamilton路徑的長度。

資料範圍

1≤n≤20
0≤a[i,j]≤107

輸入樣例:

5
0 2 4 5 1
2 0 6 5 3
4 6 0 8 3
5 5 8 0 5
1 3 3 5 0

輸出樣例:

18

思路分析:

這道題很容易想到的樸素演算法,列舉n個點的全排列。

時間複雜度為O(\(n * n!\))

使用下面的狀態壓縮dp可以優化到O(\(n^2 * 2 ^ n\))

比如在 0, 1, 2, 3 這幾個點中,我們需要從0 -> 3

0 -> 2 -> 1 -> 3 所需10

0 -> 1 -> 2 -> 3 所需20

我們肯定只選第一條路

很容易理解,若還有其他的點,第二條路總是會比第一條花費多,

所以第二條邊沒有維護的必要,可以給它刪除,保留第一條這條更優的。

注意,刪除的前提是他們二者訪問的點的個數是一樣的,現在在的點的位置是一樣的。

但樸素的爆搜還是會搜下去,直到搜完全部點。

這就是主要優化的地方。

(可以去看一下 Acwing y總的視訊講解,本人雖然知道 狀壓dp 的時間複雜度比 爆搜 要低

但一直搞不清楚它這個 狀壓dp 優化在哪,看了y總的講解恍然大悟)

最大有20個點,為了表示每個點的狀態當前點的位置

可以開一個f[1 << 20][20]來記錄 在 i 狀態 j 位置的最小代價

i 狀態 j 位置 的代價 需要從 i 狀態 下訪問過不為j 的k點來到達

只需要f[i][j] = min(f[i][j], f[i - (1<<j)][k])

下方程式碼有本人認為根據本題要求可以做出的一些小優化?註釋標註。

程式碼展示:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;

const int N = 25;
int n, g[N][N], f[1 << 20][20]; // i 狀態 j位置

inline void work (int n) {
    memset(f, 0x3f, sizeof f);
    f[1][0] = 0;
    for(int i = 1; i < 1 << n; i += 2)  // 修改  i ++ -> i += 2。遍歷狀態
        for(int j = 1; j < n; j ++) if((i >> j) & 1) // j = 0 -> j = 1。 遍歷結束點
                for(int k = 0; k < n; k ++) if((i ^ 1<<j)>>k & 1) //遍歷到結束點的點
                        f[i][j] = min(f[i][j], f[i ^ 1<<j][k] + g[k][j]);
    printf("%d", f[(1 << n) -1][n - 1]);
}

int main()
{
    scanf("%d", &n);
    for(int i = 0; i < n; i ++)
        for(int j = 0; j < n; j ++)
            scanf("%d", &g[i][j]);
    work(n);
    return 0;
}