求把 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
(注:只考虑横向摆放状态,计算出所有横向的长方形摆放种类,所有纵向长方形就只能填满剩余空隙)
#include <iostream>
#include <cstring>
#include <vector>
using namespace std;
const int N=12,M=1<<N; //因最终状态转移要用到第 m+1列,故 N至少要 12
//M表示某一列的最大排列方案数 而题目中最多有11行,则某列的排列方案最多有 2<<11种 (状态表示为 0~2^11-1)
int n,m;
long long f[N][M]; //f的第一位表示当前列数,第二位表示上一列的状态 (例 f[n][5]表示当状态转移到第 n列时,第 n-1列摆放为 1001的情况下,总共的方案数
vector <int> state[M]; //state用来存储可与当前状态共存的上一列状态,用于状态转移
//(例:state[3]存有所有可与当前这一列的状态(101)共存的上一列的状态
bool st[M]; //st用来表示单独一列状态是否合法(即不存在奇数个连续空行)
int main()
{
while(cin>>n>>m,n||m){
for(int i=0;i<1<<n;i++){ //遍历单独一列的所有摆放状态
st[i]=1; //假设合法
int cnt=0; //用来记录连续空行个数
for(int j=0;j<n;j++){ //遍历当前状态的每一个位置
if(i>>j&1){ //如果当前位置不为空
if(cnt&1){ //如果存在奇数个连续空行
st[i]=0;
break; //直接break 无需后续判断(优化)
}
//cnt=0;(可有可无)
}
else cnt++; //如果当前位置为空
}
if(cnt&1) st[i]=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); //将j(上一列)的状态存入state[i]中
}
memset(f,0,sizeof f); //因为有多组数据
f[0][0]=1; //开始时没有上一列,也可理解为上一列状态为 0, 所以在第一列时,上一列状态为 0的情况有 1种
for(int i=1;i<=m;i++) //表示处于第几列(0算第一列)
for(int j=0;j<1<<n;j++) //表示当前列状态
for(auto k:state[j]) //遍历数组中所有元素(遍历能与当前列状态共存的上一列状态)
f[i][j]+=f[i-1][k];
cout<<f[m][0]<<endl; //f[m][0]表示处于m+1列时,第m列状态为0(即什么都不放)的情况下,总共的方案数
}
return 0;
}
(个人理解,如有错误欢迎大佬指正)