blablablablablablablablablabla 传送门
这道题是状压dp的典型题
状态表示:
dp[i][j]
表示从0
到j
点所有经过点为i的二进制形式的最短Hamilton路径的长度。
数i的二进制形式表示点0 ~ n - 1
每个点是否被走过
例如 i = 10
,二进制表示为1010
,表示1、3
号点走过,0、2
号点没有走过
状态计算:
考虑要走到j
点之前,要经过的前一个点(设其编号为k),将集合分为n份,取最小值
dp[i][j] = min(dp[i - {j}][k] + a[j][k])
初始状态:
经过编号为0的点到达0点,此时的最短距离为0,dp[1][0] = 0
因为求最小值,所以将其它值设置为正无穷
O ( n 2 ∗ 2 n ) O(n^2 *2^n) O(n2∗2n)
#include
#include
using namespace std;
const int N = 21, M = 1 << N;
int w[N][N], f[M][N];
int main(){
int n;
scanf("%d", &n);
for(int i = 0; i < n; i++)
for(int j = 0; j < n; j++)
scanf("%d", &w[i][j]);
//求最小值,f[][]初始化为正无穷
memset(f, 0x3f, sizeof f);
//初始状态,经过编号为0的点到达0点,此时的最短距离为0
f[1][0] = 0;
//从0 ~ 2^n 枚举所有经过点的状态集合
for(int i = 0; i < 1 << n; i++)
for(int j = 0; j < n; j++) {//遍历当前点
// i >> j & 1 计算i的第j位是否为1
if(i >> j & 1){//如果集合i中包含j
//枚举所有转移到j的点k
for(int k = 0; k < n; k++){
//保证i中包含点k
//i - (1 << j) 将状态i中去掉点j
if((i >> k & 1) && j != k)//计算i - (1 << j)的第k位是否为1
f[i][j] = min(f[i][j], f[i - (1 << j)][k] + w[k][j]);
}
}
}
//最终答案为,经历过0 ~ n - 1的每个点后到达点n - 1时的最短距离
printf("%d\n", f[(1 << n) - 1][n - 1]);
return 0;
}