[acwing]291. 蒙德里安的梦想
/*
横放的方案数就等于总方案数,因为横着放完后,再竖着放是唯一的
dp[i][j] 表示第 i 列状态为 j 的方案数
状态为 j 是指:各行用 0 或 1 表示摆放状态
:若某行为 0,表示竖放或由前一列伸出
:若某行为 1,表示横放并向后一列伸出
dp[i][j] = ∑ dp[i - 1][k],要求 k 和 j 要兼容
k 和 j 要兼容表示:k | j 不能有连续奇数个 1,否则的话竖着无法放下
:k & j == 0 必须成立,不然都横着放有冲突
dp[0][0] = 1
dp[m][0]
*/
#include <iostream>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 12, M = 1 << N;
int n, m;
LL dp[N][M];
bool st[M]; // k | j 的结果状态是否兼容
int main()
{
while (cin >> n >> m && (n || m))
{
for (int i = 0; i < 1 << n; i++) // 遍历所有的结果状态
{
st[i] = true;
int cnt = 0; // 此状态连续的 0 的个数
for (int j = 0; j < n; j++)
{
if (i >> j & 1) // 如果遇到 1
{
if (cnt & 1) // 如果 cnt 是奇数
{
st[i] = false;
break;
}
cnt = 0;
}
else // 如果遇到 0
cnt++;
}
if (cnt & 1) st[i] = false; // 处理高位 0
}
memset(dp, 0, sizeof(dp)); // 多次询问每次都要清空 dp 数组
dp[0][0] = 1;
for (int i = 1; i <= m; i++) // 枚举 m 列
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])
dp[i][j] += dp[i - 1][k];
}
cout << dp[m][0] << endl;
}
return 0;
}