蒙德里安的梦想
蒙德里安的梦想
求把 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
题解
观察题目给出的摆放小方块的测试用例,可以发现总方案数等于横着摆放小方块的方案数,因为当横着摆放小方块的确定,那么竖着的小方块只需插入空位即可。即对每一种合法的横着摆放小方块的方案都可以唯一确定一种竖放的方式,那么得出结论:只需要考虑横着摆放的小方块的合法方案数。
定义 f[i][j] 表示前 i-1 列已经摆放完成,且从第 i-1 列伸到第 i 的小方块的状态为 j 的方案数 (若从第 i-1列伸出来:状态为 1,若没有伸出来:状态为 0)。
由于第 i 列的状态其实是对第 i-1 列摆放横着的小方块,所以第 i 列的状态可能任意,需要枚举它所有可能的状态。对于每个第 i 列的可能状态都需要考虑 i-1 列的状态(i-2 列横着摆放的小方块)中合法的转移。

思考状态转移:第 i 列的状态由将由第 i-1列的状态进行转移,考虑枚举所有 i-1 列可能的状态 k,若合法,就可以由上一列的状态 k 转移,即 f[i][j] += f[i-1][k]。
现在讨论如何检查是否合法,我们先检查第 i 列的状态 j 能否成功拼接在第 i-1 列的状态 k 上,即 j&k==0,然后在检查当 j 和 k 拼接后的状态是否具有连续的偶数个 0,若是奇数个,则无法将竖着的小方块插入,最终无法完全覆盖整个矩形,这一步可以使用一个st数组进行预处理,将所有具有连续的偶数个 0 的状态设置为 true。
初始状态为 f[0][0] = 1,因为第 -1 行无法伸到第 0 行,同时根据状态的定义,最终的答案为 f[m][0],即从第 m-1 行伸到 m 行,且状态为 0 的方案数。
最后代码如下:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
using ll = long long;
const int N = 12, M = 1 << N;
ll f[N][M];
bool st[M];
int n, m;
int main()
{
while (cin >> n >> m, (n || m)) {
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;
break;
}
cnt = 0;
} else cnt ++;
}
if (cnt & 1) st[i] = false;
}
memset(f, 0, sizeof f);
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;
}

浙公网安备 33010602011771号