求把 N×M
的棋盘分割成若干个 1×2
的长方形,有多少种方案。
例如当 N=2,M=4
时,共有 5
种方案。当 N=2,M=3
时,共有 3
种方案。
如下图所示:
2411_1.jpg
输入格式
输入包含多组测试用例。
每组测试用例占一行,包含两个整数 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
我们需要求出的横向小方格的合法摆放方案数,然后剩下的格子就依次摆放纵向小方格即可。
用 f[i][j] 表示在第 i 列的状态为 j (j是一个二进制数)时的方案数。st[i] 表示状态 i 是否合法,合法的状态是指没有连续奇数个 0 的状态。这个预处理的目的是为了后续的动态规划转移时判断是否可以将当前状态转移到下一状态。
接下来进入动态规划的过程:
首先初始化第一列 f[0][0] = 1,表示在第一列没有任何骨牌时,只有一种覆盖方法。
然后从第二列开始遍历到第 m 列,对于每一列,遍历当前状态 j,以及上一列的状态 k。如果满足以下条件:
j 和 k 没有交集(即 (j & k) == 0),这保证了当前列和上一列没有重叠的部分;
j 和 k 组合在一起的状态是合法的(即 st[j | k] == true),这保证了新状态的合法性。
那么就可以将上一列的方案数加到当前状态上,即 f[i][j] += f[i - 1][k]。
最后,输出 f[m][0],即在最后一列状态为空时的方案数,即为答案。
这样,通过动态规划遍历每一列、每一种状态的组合情况,得到的 f[m][0] 即为棋盘被完全覆盖的方案数。
#include <iostream>
#include <algorithm>
#include <cstring>using namespace std;const int N = 12, M = 1 << N;
long long f[N][M];
bool st[M];int main ()
{int n, m;while(cin>>n>>m, n || m){memset(f, 0, sizeof f);for(int i = 0; i < 1 << n; i ++ ) // 预处理一下所有的状态是不是存在连续奇数个0,枚举所有状态{st[i] = true;int cnt = 0; // 当前这一段0的个数for(int j = 0; j < n; j ++ ) // 枚举每一位if(i >> j & 1) //如果当前位为1,说明上一段0已经截止了{if(cnt & 1) st[i] = false; //说明上段存在奇数个0,不合法cnt = 0; //重新置0,用于下一段判断}else cnt ++;if(cnt & 1) st[i] = false; // 判断剩余的一段0}f[0][0] = 1;for(int i = 1; i <= m; i ++ ) // 枚举每一列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]) // 判断转移条件,首先不能冲突,其次是合法状态(j或k就是当前放完的状态)f[i][j] += f[i - 1][k];printf("%lld\n", f[m][0]); //f[i][j] 表示在第 i 列(从 0 开始计数)且状态为 j 的情况下,合法的排列总数//而题目要求的是在最后一列(第 m 列)且状态为全 0 的情况下的排列总数,因此答案存放在 f[m][0] 中}return 0;
}