给定一张 n n n 个点的带权无向图,点从 0∼ n − 1 n−1 n−1 标号,求起点 0 到终点 n − 1 n−1 n−1 的最短 Hamilton 路径。
Hamilton 路径的定义是从 0 到 n − 1 n−1 n−1 不重不漏地经过每个点恰好一次。
第一行输入整数 n n n。
接下来 n n n 行每行 n n n 个整数,其中第 i i i 行第 j j j 个整数表示点 i i i 到 j j j 的距离(记为 a [ i , j ] a[i,j] a[i,j])。
对于任意的 x , y , z x,y,z x,y,z,数据保证 a [ x , x ] = 0 , a [ x , y ] = a [ y , x ] a[x,x]=0,a[x,y]=a[y,x] a[x,x]=0,a[x,y]=a[y,x] 并且 a [ x , y ] + a [ y , z ] ≥ a [ x , z ] a[x,y]+a[y,z]≥a[x,z] a[x,y]+a[y,z]≥a[x,z]。
输出一个整数,表示最短 Hamilton 路径的长度。
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 n n 个点的全排列,计算路径长度取最小值,时间复杂度为 O ( n ∗ n ! ) O(n * n!) O(n∗n!),使用下面的二进制状态压缩 DP 可以优化到 O ( n 2 ∗ 2 n ) O(n^2 * 2^n) O(n2∗2n)。
在任意时刻如何表示哪些点已经被经过,哪些点没有被经过?可以使用一个 n n n 位二进制数,若其第 i i i 位( 0 ≤ i < n 0 \le i \lt n 0≤i<n) 为 1,则表示第 i i i 个点已经被经过,反之未被经过。
在任意时刻还需要知道当前所处的位置,因此可以使用 F [ i , j ] ( 0 ≤ i < 2 n , 0 ≤ j < n ) F[i,j](0 \le i \lt 2^n, 0 \le j \lt n) F[i,j](0≤i<2n,0≤j<n) 表示 “点被经过的状态” 对应的二进制数为 i i i,且目前处于点 j j j 时的最短路径。
在起点时,有 F [ 1 , 0 ] = 0 F[1,0] = 0 F[1,0]=0,即只经过了点 0( i i i 只有第 0 位为 1),目前处于起点 0,最短路径长度为 0。为了方便起见,将 F F F 数组其他的值设为无穷大。目标是 F[(1 << N) - 1, n - 1]
,即经过所有点 ( i i i 的所有位都是 1),处于终点 n − 1 n - 1 n−1 的最短路。
在任意时刻,有公式 F[i,j] = min{F[i ^ (1 << j), k] + weight(k,j)}
,其中 0 ≤ k < n 0 \le k \lt n 0≤k<n 并且 ((i >> j) & 1) = 1
,即当前时刻 “被经过的点的状态” 对应的二进制数为 i i i,处于点 j j j。因为 j j j 只能被恰好经过一次,所以一定是刚刚经过的,故在上一时刻 “被经过的点的状态” 对应的二进制数的第 j j j 位应该赋值为 0,也就是 i ^ (1 << j)
。另外,上一时刻所处的位置可能是i ^ (1 << j)
中任意一个是 1 的数位 k k k,从 k k k 走到 j j j 需经过 weight(k,j)
路程,可以考虑所有这样的 k k k 取最小值。这就是以上公式的由来。
#include
#include
using namespace std;
const int N = 1 << 20;
int F[N][20]; //F[i][j]表示被经过的点的状态为i,目前处于点j的最短路径
int weight[20][20];
int hamilton(int n) {
memset(F, 0x3f, sizeof(F));
F[1][0] = 0;
for (int i = 1; i < 1 << n; i++) {
for (int j = 0; j < n; j++) {
if ((i >> j) & 1) { //第j位为1
for (int k = 0; k < n; k++) {
if ((i ^ (1 << j)) >> k & 1) { //当前在k点,要去j点
F[i][j] = min(F[i][j], F[i ^ (1 << j)][k] + weight[k][j]);
}
}
}
}
}
return F[(1 << n) - 1][n - 1];
}
int main() {
int n;
cin >> n;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
cin >> weight[i][j];
}
}
cout << hamilton(n) << endl;
return 0;
}