Appearance
简介
我们从一个案例入手状压 DP。
假设我们有一张棋盘:
现在摆出了棋盘的第
设
由于第一行是题目给定的,因此我们有边界条件:
目标是求出
题目要求棋子不相邻,而第
于是列出状态转移方程:
看起来很差,而且 C++ 并不支持这种形式。怎么办呢?
采用 状态压缩 算法。可以用
采用 状态压缩 表示状态的动态规划,就是状压 DP。
例
在
预处理
每一行的状态都用一个二进制数表示:
需要进行预处理:
- 筛选出所有符合题目条件的状态;
- 计算并保存每种合法状态对应的国王个数。
由于每行有
cpp
int n, tot; // tot: 合法的状态个数
int state[]; // state[i]: 第 i 种合法的状态
int nums[]; // nums[i]: 第 i 种状态的国王数
void pre() {
// 枚举 i 为每一种状态
for (int i = 0; i < (1 << n); i ++) {
// 如果不为 0,则存在相邻的国王,不合法
if (i & (i << 1))
continue;
int cnt = 0; // 记录状态 i 中的国王数
for (int j = i; j; j >>= 1)
if (j & 1) cnt ++;
state[++ tot] = i;
nums[tot] = cnt;
}
}
分析
- 棋盘的前
行已经摆好; - 第
行的状态是 ; - 一共摆了
个国王。
问题的解是
cpp
int f[][][], ans;
// 若当前行的状态是 a,判断上一行的状态能否是 b
bool avl(int a, int b) {
return (!(a & b)) && (!((a << 1) & b)) && (!((a >> 1) & b));
}
void dp() {
f[0][1][0] = 1;
for (int i = 1; i <= n; i ++) // 放前 i 行
for (int s = 1; s <= tot; s ++) // 第 i 行的状态是 s
for (int k = nums[s]; k <= m; k ++) // 一共摆了 k 个国王
for (int t = 1; t <= tot; t ++) // 第 i - 1 行的状态是 t
if (avl(state[s], state[t]))
f[i][s][k] += f[i - 1][t][k - nums[s]];
for (int i = 1; i <= tot; i ++)
ans += f[n][i][m];
cout << ans;
}