简介

状压 DP 是动态规划的一种,通过将状态集合转化为整数记录在 DP 状态中来实现状态转移的目的。

为了达到更低的时间复杂度,通常需要寻找更低状态数的状态。大部分题目中会利用二元状态,用 位二进制数表示 个独立二元状态的情况。

使用状态压缩通常涉及位运算,关于基础位运算详见 位运算 页面。

例题 1

的棋盘里面放 个国王(),使他们互不攻击,共有多少种摆放方案。

国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共 个格子。

解释

表示前 行,第 行的状态为 ,且棋盘上已经放置 个国王时的合法方案数。

对于编号为 的状态,我们用二进制整数 表示国王的放置情况, 的某个二进制位为 表示对应位置不放国王,为 表示在对应位置上放置国王;用 表示该状态的国王个数,即二进制数 的个数。例如,如下图所示的状态可用二进制数 来表示(棋盘左边对应二进制低位),则有

设当前行的状态为 ,上一行的状态为 ,可以得到下面的状态转移方程:

设上一行的状态编号为 ,在保证当前行和上一行不冲突的前提下,枚举所有可能的 进行转移,转移方程:

实现

Note

此题的状态有 行、列、是否放置国王 3 个维度,通过压缩状态集合,将一行的国王分布的状态压缩为一个整数,从而将列和是否放置国王的状态合并到了一起,减少了状态数。

例题 2

个人需要过桥,第 的人的重量为 ,过桥用时为 . 这些人过桥时会分成若干组,只有在某一组的所有人全部过桥后,其余的组才能过桥。桥最大承重为 ,问这些人全部过桥的最短时间。

.

解释

我们用 表示所有人构成集合的一个子集,设 表示 中人的最长过桥时间, 表示 中所有人的总重量, 表示 中所有人全部过桥的最短时间,则:

需要注意的是这里不能直接枚举集合再判断是否为子集,而应使用 子集枚举,从而使时间复杂度为 .

转移方程详解

状态定义:

  • :需要过桥的人的集合(用二进制表示)
  • 的子集,表示第一组过桥的人
  • :集合 中所有人全部过桥的最短时间
  • :集合 中所有人的最长过桥时间(同组人同时过桥,取最大值)
  • :集合 中所有人的总重量

转移逻辑:

为了让集合 的所有人过桥,我们将其分成两部分:

  1. 第一组 :先过桥,用时 (必须满足
  2. 剩余的人 :等第一组过完后再过桥,用时

总时间 = ,枚举所有合法的 ,选择使总时间最小的方案。

示例: 假设 (二进制 111

第一组 二进制剩余 总时间
001110
010101
011100
101010

选择所有方案中时间最小的。

关键点:

  • 用位运算表示为 S ^ T(异或),前提是
    • 原理:对于每一位,如果 为 1 且 为 0,则结果为 1(保留);如果都为 1,则结果为 0(去掉)
    • 等价写法:S & (~T) 也可以表示集合差,但 S ^ T 更简洁
  • 必须枚举 的所有子集,而不是简单的循环
  • 递推顺序:从小集合到大集合,确保计算 时, 已经计算过

实现

Note

此题中用一维状态表示了子集

习题


补充说明

为什么 可以用 S ^ T 表示?

在状态压缩 DP 中,集合用二进制数表示,集合差运算可以通过位运算高效实现。

前提条件: 的子集)

原理分析:

对于二进制表示的每一位(代表一个元素):

的第 的第 的第 S ^ T 结果说明
0000 ^ 0 = 0元素不在 中 ✓
1011 ^ 0 = 1元素在 中但不在 中(保留)✓
1101 ^ 1 = 0元素在两个集合中(去掉)✓
01-不可能出现(违反

结论: 时,异或操作 S ^ T 恰好等于集合差

具体示例:

假设 (二进制 111),(二进制 101

S       = 111  (包含元素 0, 1, 2)
T       = 101  (包含元素 0, 2)
S ^ T   = 010  (只包含元素 1)

验证:

等价写法:

除了异或,还可以使用 ” 与非 ” 操作:

S & (~T)  // 也能得到 S \ T

对比:

方法表达式优点缺点
异或S ^ T简洁,不需要取反语义不太直观
与非S & (~T)语义清晰:” 在 中且不在 中 “需要额外的取反操作

在状态压缩 DP 中,S ^ T 是惯用写法,更加简洁高效。

代码示例:

int S = 0b111;   // {0, 1, 2}
int T = 0b101;   // {0, 2}
 
// 两种等价方式计算集合差
int diff1 = S ^ T;      // 0b010 = {1}
int diff2 = S & (~T);   // 0b111 & 0b010 = 0b010 = {1}
 
assert(diff1 == diff2);  // 两者相等

此文件夹下有0条笔记。