二进制状态压缩, 最短Hamilton路径
二进制状态压缩
使用一个长度为 $m$ 的二进制数来表示并存储一个长度为 $m$ 的 $bool$ 数组
通过一系列操作可以实现原 $bool$ 数组中对应下标的存取
| 操作 | 运算 |
|
取出整数n在二进制表示下的第k位 取出整数n在二进制表示下的第0 ~ k-1位 把整数n在二进制表示下的第k位取反 对整数n在二进制表示下的第k位赋值1 对整数n在二进制表示下的第k位赋值0 |
n >> k & 1 n & ((1 << k) - 1) n xor (1 << k) n | (1 << k) n & (~(1 << k)) |
最短Hamilton路径
最短Hamilton路径的定义为从起点0到终点 $n-1$ 不重不漏地经过每个点恰好一次
给定一张 $ n(n \leq 20) $ 个点的带权无向图, 点从0 ~ $n-1$ 编号, 求从起点0到终点 $n-1$ 的最短Hamilton路径
暴力求解, 枚举 $n$ 个点的全排列, 时间复杂度为 $O(n \cdot n!)$, 可以使用二进制状态压缩dp来优化到 $ O(n^{2} \cdot 2^{n}) $
状压dp求解
状态表示
使用一个 $n$ 位二进制数来表示哪些点已经被经过, 哪些点还没被经过, 具体为若其第 $i$ 位为1, 则表示经过, 反之未经过
还需要多开一维, 记录当前状态下在处于哪个点
所以状态表示为 $ dp[(1 << N)][N] $, 第一维表示点被经过的状态, 第二维表示当前所处的点
状态计算
在起点处的状态 $ dp[1][0] $ 初始化为0, 其余点求 $min$ 初始化为INF, 最终答案就是求解 $ dp[(1 << n) - 1][n - 1] $
对于每个状态 $dp[i][j]$ (状态为1, 停在 $j$ 点)来说, 可以遍历除当前所处点 $j$ 的其余位置上为1的点, 然后取 $min$
即 $ dp[i][j] = min(dp[i][j], dp[i \; xor \; 1 << j][k] + w[k][j]]) $ (找到这个状态是由哪个状态转移过来的, 即上一步所处点)
保证求解过程满足拓扑序
保证求解每个状态时去掉该位置的1已经被求解过了, 即从小到大求解
void solve() {
//初始化所有状态的距离为INF, 状态为1目前在0点的距离为0
memset(dp, 0x3f, sizeof(dp));
dp[1][0] = 0;
for(int i = 0; i < 1 << n; i++) //从小到大枚举每个状态
for(int j = 0; j < n; j++) //枚举当前状态下所处的点
if(i >> j & 1) //状态中第j位得是1才行
for(int k = 0; k < n; k++) //枚举上一个所处的点
if((i ^ 1 << j) >> k & 1) //去点j点的1状态中第k位也得是1才行
dp[i][j] = min(dp[i][j], dp[i ^ 1 << j][k] + w[k][j]);
cout << dp[(1 << n) - 1][n - 1]; //(1 << n) - 1就是所有点都被经过(所有点都是1)
}

浙公网安备 33010602011771号