[AcWing] 91. 最短Hamilton路径(C++实现)状态压缩dp例题
1. 题目
2. 读题(需要重点注意的东西)
思路:
状态压缩:即用二进制表示状态
用状态压缩表示哪个位置被用过了
如有如下五个节点
0 1 2 3 4
则 state = 10011 ,表示第0、1、4个节点被使用了
怎么用状态压缩求出最短路径呢?
我们用二进制表示各个节点选与不选的情况,然后计算每个节点,如果选了它,则进行dp:
f[ state ][ j ] = f[ state_k ][ k ] + weight[ k ][ j ](该式见如下dp分析)
dp分析
代码实现思路:
① 对于每一个状态i,枚举每一个节点j,如果选择了j这个节点
② 在每一个节点去除当前 j 的状态后,遍历满足条件 i >> k & 1 == 1的k
③ 然后求f[i][j] = min(f[i][j], f[i - (1 << j)][k] + w[k][j]),找出从 k 到 j 的最短路径
④ 最后输出f[(1 << n) - 1][n - 1],表示枚举完了所有的2的n次方减1种状态,最终节点到n-1的最短路径,即为最终答案。
3. 解法
---------------------------------------------------解法---------------------------------------------------
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 20, M = 1 << N;
int n;
int w[N][N];
int f[M][N]; // f[i][j] 表示状态为i的情况下,停在点j,此时的最短路径是多少
// f[state][j] = f[state_k][k] + weight[k][j]
// state_k = state 除掉j之后的集合,且state_k要包含k
// state怎么来表示一个集合呢? ---> 状态压缩
int main()
{
cin >> n;
// 读入所有边权
for (int i = 0; i < n; i ++ )
for (int j = 0; j < n; j ++ )
cin >> w[i][j];
memset(f, 0x3f, sizeof f);
f[1][0] = 0;
// 用二进制枚举所有的状态 ..0000、..0001、..0010、..0011、...
// 每一个点都有用或不用两种状态,因此有2的n次方种状态
// ①
for (int i = 0; i < 1 << n; i ++ )
for (int j = 0; j < n; j ++ )
// 判断i的第j位是否为1,i右移j为与1
// 为1表示经过了该点
// ②
if (i >> j & 1)
for (int k = 0; k < n; k ++ )
if (i >> k & 1)
// ③
// i - (1 << j) 表示 i 除去 j 后的状态
f[i][j] = min(f[i][j], f[i - (1 << j)][k] + w[k][j]);
//④
cout << f[(1 << n) - 1][n - 1]; // 表示把所有状态都遍历过了,然后停在第n-1位
return 0;
}
可能存在的问题(所有问题的位置都在上述代码中标注了出来)
4. 可能有帮助的前置习题
5. 所用到的数据结构与算法思想
- 动态规划
- 状态压缩dp
6. 总结
状态压缩dp的例题,理解思想并自行推导出代码。