算法竞赛进阶指南-3.最短Hamilton路径
题目链接
汉密尔顿路径是一道旅行商问题,已被证明是NP-Hard问题,无法在多项式级别的复杂度求出。
如果用暴力来做,首先要枚举所有路径, 相当于求n个点的全排列,即,
然后枚举完之后要遍历一次,求整体的路径长度, 即 ,这个时间复杂度太高了。
Method : 状态压缩DP
不难发现状态是比较多的,但对于每一个节点,都只关心0(未访问)和1(已访问)这两种状态。
因此可以考虑用一个N位2进制数来表示状态集合,用之间的一个十进制数形式表示DP状态的其中一维。
状态表示 :所有从0走到j,走过的所有点的情况是i的所有路径, 求min。
状态划分 :枚举上一个状态,即0->·····–>k–>j中k的所有可能情况。
状态转移:枚举走到j点前的一个状态=>[i - (1 << j)
能把 i
的第 j
位 置为0
]
上一个状态中的所处的位置,可能是 i - (1 << j)
中任意一个是1的数位k,从k到j需要花费g[k][j]
因此 f[i][j] = min(f[i][j], f[i - (1 << j)][k] + g[k][j])
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 20;
const int M = 1 << N; // int一共32位
//无向图
int g[N][N];
//状态
int f[M][N];
int n;
int main() {
cin >> n;
for (int i = 0; i < n; i ++) {
for (int j = 0; j < n; j ++) {
scanf("%d", &g[i][j]);
}
}
// 起点0 => 二进制表示: 00...01
memset(f, 0x3f, sizeof f);
f[1][0] = 0;
for (int i = 0; i < 1 << n; i ++) {
for (int j = 0; j < n; j ++) {
// 如果i的第j位是1,说明j点可达
if (i >> j & 1) {
// 枚举走到j点前的一个状态,以k为终点的最短距离
for (int k = 0; k < n; k ++) {
// i - (1 << j)为走到j点的前一个状态,k是前一个状态中的可达点
if (i - (1 << j) >> k & 1) {
f[i][j] = min(f[i][j], f[i - (1 << j)][k] + g[k][j]);
}
}
}
}
}
cout << f[(1 << n) - 1][n - 1] << endl;
return 0;
}
复杂度分析
时间复杂度,其中为二进制状态压缩(i层循环),为枚举j层和枚举k层的两重循环。
空间复杂度,用于存储图
g[N][N]
。
本博客所有文章均采用 CC BY-NC-SA 4.0 协议 ,禁止商用,转载请注明出处!