状压 dp 入门

引入

先看题目:

P1896 [SCOI2005] 互不侵犯 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

[SCOI2005] 互不侵犯

题目描述

\(N \times N\) 的棋盘里面放 \(K\) 个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共 \(8\) 个格子。

输入格式

只有一行,包含两个数 \(N,K\)

输出格式

所得的方案数。

样例 #1

样例输入 #1
3 2
样例输出 #1
16

数据范围及约定

对于全部数据,\(1 \le N \le 9\)\(0 \le K \le N\times N\)


\(\text{upd 2018.4.25}\):数据有加强。

显然这题可以用 dfs 做,但是实测会超时:

那么有没有更好的方法呢?

原理

既然是 dp,肯定要有 \(3\) 样:

  • 状态表示
  • 状态转移
  • 边界条件

而状压 dp 的精髓在于状态表示。

以上面那题为例:

  • \(dp(i,j,S)\) 表示选第 \(i\) 行时,\(1\sim i\) 行共用了 \(j\) 个王,第 \(i\) 行所用的格子集合为 \(S\)
    其中 \(S\) 使用一个数表示:\(S\) 的二进制表示中从左到右第 \(k\) 位表示 \(i\)\(k\) 列的情况。\(1\) 表示选,\(0\) 则表示不选。
  • \(dp(i,j,S)=\sum dp(i-1,j-\text{popcount}(S),S')\;\;,j\ge \text{popcount}(S)\)
    注意 \(S,S'\) 必须合法。下文会解释如何检查合法性。\(\text{popcount}(x)\) 表示 \(x\) 的二进制表示中 \(1\) 的数量。这里 \(j-\text{popcount}(S)\) 就是减去了第 \(i\) 行用的王,剩下 \(1\sim i-1\) 行用的。
  • \(dp(0,0,0)=1\)

如何判断合法?

  • 首先本行内不能有王相邻,即 \(S\And({S\gg1})=0~\land~ S'\And({S'\gg1})=0\)。如果位移并与后有任意一位是一,说明 \(S\) 中有相邻的 \(1\)
  • 其次上下行不能有相邻,即 \(S'|(S'\gg1)|(S'\ll1)\And S=0\)。可以自行体会。

\(S,S'\) 满足上述条件即为合法。

然后编码实现即可。注意要先枚举 \(i\),再枚举 \(S,S'\),最后枚举 \(j\)

如何输出答案?

枚举第 \(N\) 行所有合法情况并累加即可。

即:

\[\sum_{S=0}^{2^n-1}dp(N,K,S) \]

习题


EOF

posted @ 2023-10-13 13:53  Po7ed  阅读(9)  评论(0编辑  收藏  举报