哈密頓路徑的定義是從起始點到結束點不重不漏地經過每個點恰好一次。
那麼哈密頓路徑的所有情況就是0123..n數列的全排列了。
如果要求出最短路徑, 枚舉全排列的話時間複雜度就達到了o(n*n!)級別
這道題的正解是狀態壓縮+DP
那麼什麼是狀態壓縮呢?爲什麼能用到求最短哈密頓路徑呢?
先看個實例。
有5個點,0 1 2 3 4 求其最短哈密頓路徑.
因爲每個點只能經過一次, 所以對於已經走過的點, 無法影響到後面的點.
對於0 1 2 3 4 來說 假如已經走了 0 1 2 三個點 現在只能走2 -> 3 或者2 ->4 所以假如 0 1 2 = 10 0 2 1 = 20 那麼我們一定會捨棄0 2 1 這種情況。如圖:
也就是說求最短哈密達路徑時 我們只需要保存已經走過的點的最短哈密頓路徑!!!
以n=3爲例,每個點都有兩種可能,走或者不走,所以所有的路徑情況一共有 2^3種:
0 (只走了0號點)
1
2
0 1 (走了0和1號點)
0 2
1 2
0 1 2 (走了0、1、2號點 包含0->1->2 , 0->2->1,保存時只保存其中的最小值。)
(另:雖然是以0號點爲起始點,爲了方便起見還是寫出了所有情況)
還有三個點全都不走的情況, 一共8種。本題中 n <= 20 所以 最多有2^20種情況需要保存。
假如 終點n == 4 那麼 答案 = min(0XX3->4, 0XX2->4, 0XX1->4) .(假如1 2 3號點均可以到達4號點)
而對於 0XX3 0XX2 0XX1 0XX 只是一個狀態 分別表示選擇(012)、(013)、(023)時的最短哈密頓路徑.
所以所謂的狀態壓縮就是把 一個狀態集合 用 一個二進制數表示。 對於本題來說 0表示不經過, 1表示經過。
如經過了0 1 3 號點時的最短路徑的狀態就用 S = 01011來表示. ( 最高位表示第n號點, 最低位表示0號點)!!!!!這就是狀態壓縮!!!!
所以對於第n號點來說, 只需要求出所有能從n-1個點到達第n個點的最短哈密頓路徑,就能求出到達第n號點的最短哈密頓路徑.
同樣對於一個點i來說, 從其他點到達點i時的最短哈密頓路徑就是:
for(int j = 0; j < n; j++){
dp[S][i] = min(dp[S][i], dp[S^(1<<i)][j] + weight[j][i]);//S^(1<<i)表示沒有經過i點時的狀態
}
例題:
給定一張 n 個點的帶權無向圖,點從 0~n-1 標號,求起點 0 到終點 n-1 的最短Hamilton路徑。 Hamilton路徑的定義是從 0 到 n-1 不重不漏地經過每個點恰好一次。
輸入格式
第一行輸入整數n。
接下來n行每行n整數,其中第ii行第jj個整數表示點ii到jj的距離(記爲a[i,j])。
對於任意的x,y,zx,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 1≤n≤20
0≤a[i,j]≤10^7
輸入樣例:
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
C++:
#include <iostream>
#include <cstring>
using namespace std;
int dp[1<<20][20];
int weight[20][20];
int main(){
int n;
cin >> n;
for(int i = 0; i < n; i++)
for(int j = 0; j < n; j++)
cin>>weight[i][j];
memset(dp, 0x3f, sizeof dp);
dp[1][0] = 0;
for(int S = 1; S < 1<<n; S++){
for(int i = 0; i < n; i++){
if(S & (1<<i)){ //遍歷到了第i個點 此時第i個點的狀態爲 1
for(int j = 0; j < n; j++){
if(S & (1<<j) ) // 第j個點的狀態爲1 從第j個點到第i個點 所以第j個點一定走過了 第 i 個點一定沒有走過。
dp[S][i] = min(dp[S][i], dp[S^(1<<i)][j] + weight[j][i]);
}
}
}
}
cout<<dp[(1<<n)-1][n-1]; // 注意 - 號優先級大於 << 所以 (1<<n) - 1 注意括號。
return 0;
}
如果S爲狀態, 則當 S = 11111時 d[S][n-1]就是最終答案。 n-1是因爲下標從0開始
(1<<5) - 1 = 100000 - 1 = 11111 詳見 位運算。