最短 Hamilton 路径

状态压缩 + 动态规划 典型例题

https://www.acwing.com/problem/content/93/

给定一张 n 个点的带权无向图,点从 0n1 标号,求起点 0 到终点 n1 的最短 Hamilton 路径。

Hamilton 路径的定义是从 0n1 不重不漏地经过每个点恰好一次。

输入格式

第一行输入整数 n

接下来 n 行每行 n 个整数,其中第 i 行第 j 个整数表示点 ij 的距离(记为 a[i,j])。

对于任意的 x,y,z,数据保证 a[x,x]=0a[x,y]=a[y,x] 并且 a[x,y]+a[y,z]a[x,z]

输出格式

输出一个整数,表示最短 Hamilton 路径的长度。

数据范围

1n20
0a[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

题解

状态表示

dp(i,j):= 从节点 0 走到节点 j,经过的节点状态为 i 的所有路径的距离的最小值

dis(i,j):= 从节点 i 走到节点 j 的距离。

对于经过的节点状态为 i,我们采用状态压缩的方式来表示它:i 为一个 20 位二进制数,其第 n 位的状态代表是否走过第 n 个点。

举例来说,若 i=0100,0000,0000,0000,0011b,则代表经过了第 0,1,18 个点,其他点没有经过。

状态转移

令节点 j 的前一个节点为 k,即路径为:0kj

那么可知:dp(i,j)=dp(i{j},k)+dis(k,j)

由于 dp(i,j) 储存的是最小值,因此对于 k=0,1,,n1,取所有答案的最小值即可。

初值

dp(1,0)=0:从 0 走到 0,距离为 0,经过了第 0 个点,因此 i=1,j=0,dp(i,j)=0

代码

#include <bits/stdc++.h>

using namespace std;

const int MAXN = 16;
int n;
int a[MAXN][MAXN];
int dp[1 << MAXN][MAXN];

int main()
{
    cin >> n;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            cin >> a[i][j];
    memset(dp, 0x3f, sizeof(dp));
    dp[1][0] = 0;
    for (int i = 0; i < 1 << n; i++)
        for (int j = 0; j < n; j++)
            if (i >> j & 1)
                for (int k = 0; k < n; k++)
                    if ((i - (1 << j)) >> k & 1)
                        dp[i][j] = min(dp[i][j], dp[i - (1 << j)][k] + a[k][j]);
    cout << dp[(1 << n) - 1][n - 1] << endl;
    return 0;
}

重点解析

从上面的第 18 行开始

代码解析
for (int i = 0; i < 1 << n; i++)枚举所有的 i 情况
for (int j = 0; j < n; j++)枚举所有的 j 情况
if (i >> j & 1)筛去不合法情况(从 0 走到 ji 中一定包含 j
for (int k = 0; k < n; k++)枚举所有的 k 情况
if ((i - (1 << j)) >> k & 1)筛去不合法情况(从 0 走到 ki{j} 中一定包含 k
dp[i][j] = min(dp[i][j], dp[i - (1 << j)][k] + a[k][j]);状态转移,取最小值