如果采用爆搜的方式,暴力枚举每条可能的路径,时间复杂度是n!,显然会超时。
事实上,对于一段路径,如果首尾相同、且经过的点的集合也一样,那么只要取最优的一条即可。如
1 -> 3 -> 2 -> 4 总距离是10
1 -> 2 -> 3 -> 4 总距离是12
那么一定是取1 -> 3 -> 2 -> 4更优,对于1 -> 2 -> 3 -> 4的其他方案就没必要枚举了。
因此可以考虑利用状态压缩记录前面已经走过的路径,用二进制表示只需要220 。
例如n=5,走了014,那么就是10011.
f[state, j]表示当前已经走了路径状态是state,且最后位于编号j的上面。
那么f[state, j]可以从f[state_k, k]加上k到j的边权转移过来,这边的state_k是state的点集去掉j,并且含有k。
即f[state, j] = f[state_k, k] + w[k, j]
初始化f[1, 0] = 0
,其他都是正无穷。因为一开始在0号位,只有状态000…001是合法的,位于编号0上。
最后答案存在f[2^n - 1, n - 1]
中
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
int n, w[25][25], f[1 << 20][21];
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]);
}
}
memset(f, 0x3f, sizeof(f));
f[1][0] = 0;
for (int i = 0; i < 1 << n; i ++ ) {
for (int j = 0; j < n; j ++ ) {
if (i >> j & 1) {// 状态i含有j
for (int k = 0; k < n; k ++ ) {
int state_k = i - (1 << j); // 状态i去掉j,得到state_k
if (state_k >> k & 1) {// state_k含有k
f[i][j] = min(f[i][j], f[state_k][k] + w[k][j]);
}
}
}
}
}
printf("%d\n", f[(1 << n) - 1][n - 1]);
return 0;
}