题目链接:点击这里
思路:一列一列地考虑。先放横着的,再放竖着的。总方案数就等于只放横着的小方块的合法方案数。
如何判断当前方案是否合法?
所有剩余位置能否填充满竖着的小方块。也就是可以按列来看,每一列内部所有连续的空着的小方块需要是偶数个。
时间复杂度为 ,时间还是有点紧张,不过可以AC:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 12, M = 1 << N;
int n, m;
ll f[N][M];
bool st[M];
int main()
{
while(scanf("%d%d", &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; //连续的0是奇数就不合法
cnt = 0;
}
else
cnt++;
}
if(cnt & 1) st[i] = false; //最后一块连续的0是奇数也不合法
}
memset(f, 0, sizeof f);
f[0][0] = 1; //第0列前面不会有小方块伸出来
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];
}
}
}
printf("%lld\n", f[m][0]);
}
return 0;
}
优化:可以先预处理出前一列的所有合法的转移状态。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 12, M = 1 << N;
int n, m;
ll f[N][M];
bool st[M];
vector<int> state[M];
int main()
{
while(scanf("%d%d", &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; //连续的0是奇数就不合法
cnt = 0;
}
else
cnt++;
}
if(cnt & 1) st[i] = false; //最后一块连续的0是奇数也不合法
}
for(int i = 0; i < 1 << n; ++i)
{
state[i].clear();
for(int j = 0; j < 1 << n; ++j)
{
if((i & j) == 0 && st[i | j])
state[i].push_back(j);
}
}
memset(f, 0, sizeof f);
f[0][0] = 1; //第0列前面不会有小方块伸出来
for(int i = 1; i <= m; ++i) //枚举每一列
{
for(int j = 0; j < 1 << n; ++j)
{
for(auto k : state[j])
{
f[i][j] += f[i-1][k];
}
}
}
printf("%lld\n", f[m][0]);
}
return 0;
}