状压DP 学习笔记
一、基础位操作
| 具体技巧 | 核心用途 | 位运算表达式/实现 | 说明/原理 |
|---|---|---|---|
| 判断第i位是否为1 | 检查元素i是否在集合中(如城市是否已访问) | (mask & (1 << i)) != 0 |
1<<i生成第i位为1的掩码,按位与非0则第i位为1 |
| 第i位置1 | 将元素i添加到集合中(如选择任务i) | `mask | = (1 << i)` |
| 第i位清0 | 将元素i从集合中移除(如回溯取消选择) | mask &= ~(1 << i) |
~(1<<i)第i位为0、其余为1,按位与清0第i位 |
| 第i位翻转 | 切换元素i的状态(选/不选、占用/释放) | mask ^= (1 << i) |
异或1翻转第i位,异或0保留其余位 |
[========]
| 集合运算 | 判断两个集合无交集 | 检查两个状态是否无冲突(如任务不重叠)| (a & b) == 0 | 无任何一位同时为1,说明无公共元素 |
| 集合运算 | 求两个集合并集 | 合并两个状态的所有元素 | a | b | 按位或保留所有为1的位,即两个集合的全部元素 |
| 集合运算 | 求两个集合交集 | 提取两个状态的公共元素 | a & b | 仅保留同时为1的位,即公共元素 |
| 集合运算 | 枚举集合所有非空子集 | 枚举当前状态的前驱/子集(DP转移核心)| ```c++
for (int sub = mask; sub; sub = (sub - 1) & mask) {
// 处理子集sub
}
| 集合运算 | 枚举集合所有真子集 | 枚举不含自身的子集 | ```c++
for (int sub = (mask - 1) & mask; sub; sub = (sub - 1) & mask) {
// 处理真子集sub
}
``` | 初始为最大真子集,后续逻辑同非空子集枚举 |
| 集合运算 | 判断a是否为b的子集 | 检查a的元素全包含在b中 | `(a & b) == a` | 若a是b的子集,a与b的交集仍等于a |
| 计数与状态提取 | 统计掩码中1的个数 | 获取集合大小(如已访问城市数)| C++:`__builtin_popcount(mask)`<br>Python:`bin(mask).count('1')` | 手动实现:循环执行`mask &= mask-1`,每轮清除最低位1并计数 |
| 计数与状态提取 | 提取最低位的1(LSB)| 找到集合中最小的元素(如最后选择的任务)| `lowbit = mask & (-mask)` | 补码特性:-mask = ~mask + 1,按位与仅保留最低位1 |
| 计数与状态提取 | 清除最低位的1 | 移除集合中最小的元素(如回溯取消最后选择)| `mask = mask & (mask - 1)` | mask-1将最低位1置0,按位与清除该位 |
| 计数与状态提取 | 提取最高位的1(MSB)| 找到集合中最大的元素(如最早选择的任务)| C++:`1 << (31 - __builtin_clz(mask))` | `__builtin_clz`统计前导0个数,计算最高位1的位置 |
| 进阶优化技巧 | 掩码范围限制 | 仅保留前n位有效位(避免无关位干扰)| `mask & ((1 << n) - 1)` | `(1<<n)-1`生成前n位全1的掩码,按位与清除n位后的所有位 |
| 进阶优化技巧 | 异或表示状态差异 | 计算两个状态的差异位(新增/删除的元素)| `diff = a ^ b` | 异或结果中1的位置,即为两个状态的差异位 |
| 进阶优化技巧 | 预处理合法掩码 | 过滤不符合约束的状态(减少DP状态量)| 提前筛选满足条件(如1的个数为k)的掩码存入数组 | 避免DP遍历过程中重复判断约束,提升效率 |

浙公网安备 33010602011771号