状压 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)
\]
习题
- P2704 [NOI2001] 炮兵阵地 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
- Square Subsets - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
- P1879 [USACO06NOV] Corn Fields G - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
EOF