在很多DP问题中,状态往往涉及“哪些元素已经用过”或“哪些位置被占据”。如果我们用布尔数组标记,状态表示复杂且无法直接作为数组下标。此时,如果元素个数 n 较小(通常 n ≤ 20),我们可以用一个整数的二进制位来表示集合:第 i 位为 1 表示元素 i 已选(或存在),0 表示未选。这样,一个 int 或 long long 就能容纳所有子集,状态总数从无法枚举变为 2^n 个,配合递推或记忆化搜索即可求解。
二、位运算基础(必会)
在 C++ 中,整数的二进制位运算高效且直观。以下是状压DP中最常用的操作(假设 mask 是一个整数,n 位有效):
操作
表达式
含义
判断第 i 位是否为 1
mask >> i & 1
等价于 (mask & (1<<i)) != 0
将第 i 位设为 1
mask |= (1<<i)
将第 i 位设为 0
mask &= ~(1<<i)
翻转第 i 位
mask ^= (1<<i)
取出最低位的 1
lowbit = mask & -mask
枚举 mask 的所有子集
for (int sub = mask; sub; sub = (sub-1) & mask)
经典遍历
三、入门例题:最短哈密顿路径(TSP雏形)
问题描述:给定 n 个点(n ≤ 20)和点之间的距离矩阵 dist[i][j],求从起点 0 出发,经过所有点恰好一次,最后到达终点 n-1 的最短路径长度。
分析:状态需要知道“哪些点已经访问过”以及“当前在哪个点”。定义 dp[mask][i] 表示已经访问过的点集合为 mask,当前位于点 i 时的最短路径长度。转移时,从点 i 走到一个尚未访问的点 j,更新 dp[mask | (1<<j)][j]。最终答案为 dp[(1<<n)-1][n-1]。