目录
1. 二进制常用操作
1.1 在了解状压dp之前,我们需要复习一下二进制:
取出整数n在二迸制表示下的第k位: (n >>k) &1
把整数n在二逬制表示下的第 k位取反:n ^ (1<<k)
対整数n在二逬制表示下的第k位赋值1:n |(1 <<k)
対整数n在二逬制表示下的第k位赋值0:n & (~1 <<k))
1.2 二进制模拟集合:
2. 状压dp
2.1 实现方法
让我们先看一道例题:最短哈密尔顿路径 - LUKE LABS Online Judge
用二进制数标记某个点是否经过,0 未经过,1表示经过,最短路径dp[state][x]
dp[1 << n - 1][ n - 1]
对于 状态(S, j),可以枚举 当前之所以处在位置j,是上一步从哪里走过来。枚举上一步 所在的位置k。得到转移方程 :
dp[i][j] = min(dp[i][j], dp[i - (1 << j)][k] + a[k][j])
状态初值是 dp[1][0] = 0
时间复杂度分析:状态数 * 状态转移时间复杂度
2.2 代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 20, M = 1 << N;
int n, a[N][N], dp[M][N];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
// 输入n个点 和 每个点之间的距离
cin >> n;
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++) {
cin >> a[i][j];
}
// 使用dp[state][x]标记 经过二进制的state的某个点 最后停留x点上的最短距离
memset(dp, 0x3f, sizeof dp); // 0x3f 代表无穷大
dp[1][0] = 0; // 初始化 state = 1 停留在0点的最段距离为0
for (int i = 0; i < 1 << n; i++) // 遍历各种组合
for (int j = 0; j < n; j++) // j,k组合是0~n-1
if (i >> j & 1)
for (int k = 0; k < n; k++) // state(i必须经过k点,才可以判断)
if (i >> k & 1) { // 判断i在二进制下的第k位是否为1
// 某个state二进制记录下,找出停留在k点上再加上k到j的距离,得到state二进制下,停留在j上的最短路径
dp[i][j] = min(dp[i][j], dp[i - (1 << j)][k] + a[k][j]);
}
// 输出0~n-1的state下停留在n-1点的最段距离, 复杂度0(N^2 * 2^n)
cout << dp[(1 << n) - 1][n - 1] << "\n";
return 0;
}