求把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
思路:要用1*2的长方形将n*m的棋盘填满,长方形有横着和竖着两种摆放方式,在计算方案数时,只需考虑横着的即可,因为只要横着的固定了,竖着的只能放在剩余的空的位置,也就自然确定了。
这里采用状态压缩dp,这里所谓的状态压缩,其实就是用二进制数的每一位的数值(0或1)表示对应位置的状态,一个二进制数就表示了竖着的一列所有的位置的状态组成的局面,具体来说,就是:
用f[i][j]表示前i-1列已经填满并且j的二进制的每一位的数值为0或1就表示当前前第i-1列填满后当前j的这一位的编号对应的这一行有无多余的伸出到第i列的小方格,我们只需要用vis数组预处理出来每一列的每一段连续的空的位置的个数是否都为偶数(因为这样的位置要插入竖着的1*2的长方形,所以必须为偶数,否则当前方案不合法),然后dp一下:
f[i][j]+=f[i-1][k] 即:第i列j所示局面是由第i-1列所有k所示局面转移过来,所以方案数就是累加的关系
注:dp时,应保证:1、第i列的j状态和第i-1列的k状态不冲突,即:第i列和i-1列的同一行不多出小方格((k&j)==0)
2、j状态和k状态合法:vis[j|k]=true
完整代码:
#include <iostream>
#include <algorithm>
#include <cstring>
#define int long long
using namespace std;
const int N=12,M=1<<N;
int f[N][M];
bool vis[M];//vis[i]表示第i列每一段连续的空的位置的个数是否为偶数
signed main()
{
int n,m;
while(cin>>n>>m,n||m){
//预处理出来每一列列每一段连续的空的位置的个数是否为偶数
for(int i=0;i<1<<n;i++){//扫描每一列的二进制数i
vis[i]=true;
int cnt=0;
for(int j=0;j<n;j++){//扫描每一列的二进制数的每一位
if(i>>j&1){//判断i的第j位是否为1
if(cnt&1){//判断当前截止到第j-1位的这一段连续的0的个数是否位奇数
vis[i]=false;
cnt=0;
break;
}
}
else cnt++;//只要i的第j位不为1,当前段连续的0的个数就++
}
if(cnt&1) vis[i]=false;//判断最后一段连续的0的个数是否为偶数
}
//dp:
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((k&j)==0&&vis[j|k]){//保证第i列和i-1列的同一行不多出小方格并且j状态和k状态合法
f[i][j]+=f[i-1][k];
}
}
}
}
cout<<f[m][0]<<endl;//最后前m-1列已经填满并且没有多出来到第m列的小方格的方案个数即为答案
}
return 0;
}