题目描述
求把 N×M 的棋盘分割成若干个 1×2 的的长方形,有多少种方案。
例如当 N=2,M=4 时,共有 5 种方案。当 N=2,M=3 时,共有 3 种方案。
如下图所示:
输入
输入包含多组测试用例。
每组测试用例占一行,包含两个整数 N 和 M。
当输入用例 N=0,M=0 时,表示输入终止,且该用例无需处理。
输出
每个测试用例输出一个结果,每个结果占一行。
输入样例
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≤N,M≤11
题解
这是一道状态压缩dp的入门( 模板)题
题目大意了解后,我们发现数据范围小的离谱,而这道题的状态非常大,所以我们考虑状压dp
接下来开始想状态表示和转移方程
这道题的核心思想就是:
我们可以先放横的,再放竖的;
这里我们用 f[i,j] 表示 已经填好了前 i - 1列,考虑从第i - 1列伸到第i列的所有方案数,并且状态为 j
按照正常的dp思路,我们都是考虑倒数第二个进行思考
不妨假设前i - 1行的状态为k 那么最后的转移方程显然是 f[i,j] += f[i - 1,k];
方程想好了考虑合法二字,什么时候合法呢?
我们容易发现
1.j & k == 0(不能同时占有一个格子)
2.所有连续的空格子的长度必须是偶数
只有同时满足上面两个条件才是合法状态;
我们不妨先预处理出来满足条件2的合法状态,至于条件1,可以在方程转移的时候进行特判
最后注意一点答案应该是 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 n,m; int main() { while(cin >> n >> m && (n || m)) { for(int i = 0;i < 1 << n; ++ i) { int cnt = 0; st[i] = true; for(int j = 0; j < n; ++ j) { if(i >> j & 1) { if(cnt & 1) st[i] = false; } else cnt ++; } if(cnt & 1) st[i] = false; } 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 ((j & k) == 0 && (st[j | k])) f[i][j] += f[i - 1][k]; cout << f[m][0] << endl; } return 0; }