【题解】AcWing 291. 蒙德里安的梦想
题目传送门
题目描述
求把N*M的棋盘分割成若干个1*2的的长方形,有多少种方案。
例如当N=2,M=4时,共有5种方案。当N=2,M=3时,共有3种方案。
如下图所示:
输入格式
输入包含多组测试用例。
每组测试用例占一行,包含两个整数N和M。
当输入用例N=0,M=0时,表示输入终止,且该用例无需处理。
输出格式
每个测试用例输出一个结果,每个结果占一行。
数据范围
\(1≤N,M≤11\)
输入样例:
1 2
1 3
1 4
2 2
2 3
2 4
2 11
4 11
0 0
输出样例:
1
0
1
2
3
5
144
51205
(状态压缩DP) \(O(n*(2^n)^2)\)
(时间复杂度应该没算错qwq
思路
分析
可以看出,当我们横着放的长方形确定时,竖着放的方案必然时确定的。
但是看出很难,怎么看出也不晓得,我也是只不过能理解做法而已
那么我们就可以使用状态压缩(不知道这么说合不合理
实现
状态表示:
f[i][j]表示,第i-1列,向右突出的长方形状态为j时的方案数。
我们使用j
的二进制形式来表示状态,有突出即为1
如f[2][5]的话
5 = \((0101)\_2\)
Tips: 图中仅为示例表示突出的意思,实际我们存的i是0~m-1
状态转移:
方案数就是相加
但方案不是都合法,所有我们需要做两个判断:
- 当前方案与之前方案不冲突,即当前方案所放位置没有被i-1的突起占据
- 当前方案选择之后,与前面方案能有偶数个格子为空
一:
原因:
很好理解,不做解释了
做法:
我们可以使用 j & k == 0 来判断是否有冲突。
二:
原因:
首先,为什么是偶数个格子?
因为我们横长方形放完之后,都是竖长方形,那就必须要偶数个格子为空。
做法:
我们可以预处理每个状态是否合法
即求出一个二进制所表示的状态是否有偶数个空。
用 j | k 来表示j 状态与 k状态叠加的结果状态。
注释
long long
是因为可能会爆int
(我自己没有认真看)
对非法状态的预处理估计放在外面也是可以的。
实际填的是 0~m-1列。
其余放在代码的注释中。
C++ 代码
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 15;
int n, m;
LL f[N][1 << N];
bool st[1 << N]; // true:当前状态合法 false:当前状态非法
int main()
{
while(scanf("%d%d", &n, &m) == 2 && n || m)
{
memset(f, 0, sizeof f);
for(int i = 0; i < 1<<n; i ++) // 对非法状态的预处理
{
int cnt = 0; // 空的格数
st[i] = true;
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; // 初始化 因为第0列不会有-1列的突出过来所以有一个方案
for(int i = 1; i <= m; i ++) // 枚举列数
for(int j = 0; j < 1<<n; j ++) // 枚举当前i的状态
for(int k = 0; k < 1<<n; k ++) // 枚举i-1的状态
if((j & k) == 0 && st[j | k]) // 符合条件
f[i][j] += f[i-1][k];
printf("%lld\n", f[m][0]); // 最后的方案总数必然是m-1列没有向m列突出
}
return 0;
}