文章目录
0. 前言
状压 dp
就是采用二进制数保存状态,方便进行位运算操作。例如 八皇后、八数码问题也都是采用了状态压缩的思想来使用一个二进制数唯一对应集合中的一个状态。
关键是要体会采用二进制数来表示状态的思想,要转变传统思维,学习接收并吸收这种思想。
1. 状压dp 模板题
重点: 状压 dp
首先,如果输入数据是一个 2 * m
的矩形方格的话,那么这个方案数就等价于 f[n] = f[n-1]+f[n-2]
就是一个非常经典的 fib
数列递推公式。
这道题将问题扩展为任意边长的矩形,和 fib
数列已经没了半毛钱关系。
经过分析可以发现,仅需找到所有横放的 1x2 小方格的方案数,那么竖放的方案就是唯一的,故方案数就能够求出来了,相当于是对问题的一步简化。
思路:
- 状态定义:
f[i][j]
:枚举第i
列的第j
个状态,j
状态为第i-1
列有 1x2 的小方格伸到了第i
列中的所在行。每一行对应一个二进制位,如果第i-1
列有小方格捅到了第i
列,那么该行对应的二进制位就置成 1,这样就能通过二进制位枚举出所有的情况了。
- 状态转移:
- 分类依据:主要需要注意三列的情况,即当前枚举的第
i
列,可能捅进第i
列的第i-1
列,可能在第i-1
列与第i
列小方格冲突的第i-2
列。总结来讲就是i-2
、i-1
、i
这三列很重要- 记
k
为第i-1
列的j
,当k = 10010
,代表第i-2
列中第一行、第四行有 1x2 的小方格捅到了第i-1
列。那么状态转移的第一个条件为:第i
行的j
与第i-1
行的k
在相同二进制位上不能同时为 1。一旦同时为 1,则该行的 1x2 小方格等于重叠了,即为失败状态。判断条件为(j&k)==0
- 同理,状态转移的第二个条件,每一列的
j
代表从前一列捅进来的小方格,再算上当前列捅向下一列的小方格,就是本列的所有小方格的位置,即j|k
,其不能存在连续奇数个 0,这样才能保证竖放小格子能成功。判断条件为j|k 不存在连续奇数个 0
- 记
- 分类依据:主要需要注意三列的情况,即当前枚举的第
- 状态初始化:
f[0][0]=1
,第 0 列只能是状态 0,没有前列则没有捅进来的格子。 - 返回答案:
f[m][0]
因为第m + 1
不能有格子捅进去。 - 时间复杂度: 首先可以预处理得到状态转移的第二个条件。总共 11 个行列,那么二进制下就是
11
×
2
11
11\times 2^{11}
11×211 个状态,转移是
2
11
2^{11}
211 次,总的大概是
2^11
大概 2000 左右,即差不多4*10^7
的计算量。
2020.10.11 字节笔试的时候考过本题,原题。
再强调一遍,本题非常非常非常经典,是状压 dp
入门经典题目。
代码:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 12, M = 1 << N;
int n, m;
long long f[N][M];
bool st[M];
int main() {
while (cin >> n >> m, n || m) {
memset(f, 0, sizeof f);
for (int i = 0; i < 1 << n; ++i) {
st[i] = true;
int cnt = 0;
for (int j = 0; j < n; ++j) {
if (i >> j & 1) {
if (cnt & 1) st[i] = false;
cnt = 0;
}
else cnt ++;
}
if (cnt & 1) st[i] = false;
}
f[0][0] = 1;
for (int i = 1; i <= m; ++i)
for (int j = 0; j < 1 << n; ++j)
for (int k = 0; k < 1 << n; ++k)
if ((j & k) == 0 && st[j | k])
f[i][j] += f[i - 1][k];
cout << f[m][0] << endl;
}
return 0;
}
本题还有许多边界、初始化情况需要考虑。
- 开数组为 12,因为题目要求限制为 11,且状态转移方程中有
i-1
出现,并且是需要求解到第 11 列下一列的状态的,所以选择 12 作为数组大小 long long
存方案数,dp
问题求方案数,不取模的情况下随随便便爆int
- 提前预处理
st
数组是错误的,因为当n
不同时,其合法的状态也不同(细品) - 预处理过程中,
cnt
存取连续 0 个长度,其实可以不必清 0。因为如果长度不合法,则必然存在一段长度是奇数的连续 0,那么此时的cnt
就是一堆偶数再加上一个奇数,结果仍为奇数,还是能够判断出来的。且不存在奇数+奇数=偶数的误判,因为第一次奇数出现时,就能够将st
对应位置直接置为false