求把 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
思路:首先从横向的小长方形入手,先把横向的小长方形放置完,并且保证
①剩余的空位还能放置纵向的小长方形
②横向的小长方形不会重叠
①
如下图,我们发现以下情况有些位置不能再放置纵向的小方块,所以要实现①,要保证每一列出现的连续空位都为偶数,这样才能保证所有空位都能放下纵向的小方块。
为了枚举某一列可能出现所有情况,我们把大长方形的长的所有二进制表示一一枚举表示所有情况;
例如,一个4x4的大长方形,每一列可能出现的状况有(1表示有横向小长方形,0表示没有)
0000 0001 0010 0011
0100 0101 0110 0111
1000 1001 1010 1011
1100 1101 1110 1111
当情况为0001时,发现0连续出现3次,连续空位为奇数,不能刚好放下纵向小长方形,排除
当情况为0011时,发现0连续出现2次,连续空位为偶数,可以刚好放下纵向小长方形
②
满足了①条件,第一列的小长方形和第二列的小长方形重合了怎么办?
如下图
还需要把相邻两列可能发生的情况再进行一次枚举,假设第i列情况k1为1100,第i+1列情况k2为0011,则不会有重合,通过k1&k2来判断是否有重合来排除小长方形重合时的情况。
最终代码
#include<bits/stdc++.h>
using namespace std;
const int N=12,M=(1<<N);
typedef long long ll;
ll f[N][M];
int a,b;
bool st[M];
int main()
{
while(cin>>a>>b,a||b)
{
for(int i=0;i<(1<<a);i++)
{
int cnt=0;
//0未放置的方块连续出现的次数,如果为偶数则能放下纵向小长方形
st[i]=true;
for(int j=0;j<a;j++)
//j用来枚举每一种情况的每一位
{
if((i>>j)&1)
//i的第j位已经放置了长方形,统计上面出现的空位置出现的次数
{
if(cnt&1)
//若空位置出现的次数为奇数,此情况不可行
{
st[i]=false;
break;
}
else cnt=0;//若为偶数出现次数归0,这行可以忽略
}
else cnt++;//如果该位是0统计次数加一
}
if(cnt&1)st[i]=false;
}
memset(f,0,sizeof(f));
f[0][0]=1;
for(int i=1;i<=b;i++)//从第一列枚举到最后一列
{
for(int j=0;j<(1<<a);j++)//这是第i列
{
for(int k=0;k<(1<<a);k++)//这是第i-1列
{
if((j&k)==0&&st[j|k])
//(j&k)判断是否有重合
//(j|k)是i-1列插入了第i列之后第i列的情况,再用st数组判断是否可行
{
f[i][j]+=f[i-1][k];//状态转移
}
}
}
}
cout<<f[b][0]<<endl;
/*
f[b]代表第b-1列已经伸出来到b列,
并且第b列还没开始往外伸的时候(伸出来就超过大长方形了)
*/
}
}