【題解】Acwing 91 最短Hamilton路徑
阿新 • • 發佈:2020-12-12
最短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;
}