题目类型 状压DP
题目意思
给出一个 n * m (1 <= n, m <= 11)的空白矩阵 问用 1×2 或 2×1 的小矩阵填満整个空白矩阵的方法数有多少个
解题方法
由于行和列最多只有11 所以可以用状态压缩 把一行的放置状态压缩为一个整数然后进行dp
dp[i][j] -> 前i行 第i行状态为j的方法数有多少个
其中状态 j 的规则 : 对于第i行 某一位上如果是被 1 * 2 (即横放)的小矩阵覆盖 或 被 2*1的小矩阵的下面一格覆盖 , 则这一位为 1 其他情况这一位为 0
这样设计规则的话 第 i-1 行向 第 i 行转移时 (第 i 行的状态只和第 i-1 行状态有关和i-1上面的行均无关系, 因为那些行的状态影响不了第 i 行)
如果第 i-1行有0状态位的话 那么第 i 行对应位肯定要置为 1 (即放置一个2×1的小矩阵) 然后就可以枚举第 i 行其他未设状态的位的情况 (可以用DFS)
其他位这时只有一种放置情况就是连续两格放一个 1*2 的小矩阵 (因为放置2×1的小矩阵的话这一位依然是0相当于没放, 这一个情况是会被下一行
放置一个 2*1的小矩阵所考虑的)
详细转移过程看代码 可以用滚动数组优化内存
参考代码 - 有疑问的地方在下方留言 看到会尽快回复的
#include <iostream> #include <cstdio> #include <cstring> using namespace std; typedef long long LL; LL dp[2][2100]; bool vis[20]; int m, now; void DFS(int i, int j) { if(i == m) { int tmp = 0; for( int k=0; k<m; k++ ) if(vis[k]) tmp += (1<<k); dp[now^1][tmp] += dp[now][j]; return ; } if(i+1 < m && vis[i] == false && vis[i+1] == false) { vis[i] = vis[i+1] = true; DFS(i+2, j); vis[i] = vis[i+1] = false; } if(i < m) DFS(i+1, j); } int main() { int n; while(scanf("%d%d", &n, &m), n || m) { now = 0; memset(dp, 0, sizeof(dp)); dp[now][(1<<m)-1] = 1; //边界值, 刚开始假设最上面一行上面有一行已经填满的行 for( int i=0; i<n; i++ ) { for( int j=0; j<(1<<m); j++ ) { // 枚举第 i-1 行的状态去更新第 i 行, 第-1行就是刚刚的边界值 if(dp[now][j] == 0) continue; memset(vis, 0, sizeof(vis)); for( int k=0; k<m; k++ ) { if((j & (1<<k)) == 0) vis[k] = true; // 首先把 要放置 2×1矩阵的先放了 } DFS(0, j); // 再枚举放 1*2矩阵的 } now ^= 1; //滚动数组优化内存 for( int j=0; j<(1<<m); j++ ) dp[now^1][j] = 0; } cout<<dp[now][(1<<m)-1]<<endl; // 注意要用 long long } return 0; }