由于长宽都比较小,采用棋盘式状态压缩DP解决,此类任务一般是在棋盘内填各种形状的块,然后求方案数。
核心思想是:总方案数等于合法横向摆放小矩形方案数之和。下面的图形中,绿色为横向填充,黄色为纵向填充,图1是合法的,图2则是非法的。
设状态矩阵为dp[i][j],其中i表示第i列,j表示横跨第i-1列和第i列的横向小矩阵的行号的二进制表示,如:
同时,存在如下约束条件:
j&k == 0
(设i-1列的状态为k)- 摆完第i列后,第i列中放置纵向小矩阵的连续空格数要为偶数个,因此
j|k
中不能存在连续奇数个空格,这个可以通过预处理获得
对于第二个约束条件,很多题解说的是
考虑当第i列摆放完后,第i列中不能存在连续的奇数个空格,即j|k中不存在连续奇数个0
,其中第i列中不能存在连续的奇数个空格
其实是不对的,因为第i列中有些空格属于横向小矩阵的头,这些空格应该作为1来考虑
初始状态:dp[0][0]=1
,状态转移公式:dp[i][j] = sum(dp[i-1][k])
,其中0 <=k<= 1<<n
(n为行数)
代码如下:
// // Created by jt on 2020/9/28. // 蒙德里安的梦想:https://ac.nowcoder.com/acm/contest/1046/A #include <iostream> #include <cstring> // memset using namespace std; const int M = 12, N = 1 << M; long long dp[M][N]; bool state[N]; // state[i]==true表示i中连续0的个数都为偶数 int main() { int m, n; // m为行数,n为列数 while (cin >> m >> n, m && n) { memset(dp, 0, sizeof(dp)); for (int i = 0; i < 1 << m; ++i) { state[i] = true; int cnt = 0; for (int j = 0; j < m; ++j) { // 注意&的优先级低于== if ((i >> j & 1) == 0) ++cnt; else { if((cnt & 1) == 1) {state[i] = false; break; } else cnt = 0; } } if ((cnt & 1) == 1) state[i] = false; } dp[0][0] = 1; // 初始状态 for (int i = 1; i <= n; ++i) { for (int j = 0; j < 1 << m; ++j) { for (int k = 0; k < 1 << m; ++k) { // 满足两个约束条件 if ((state[j|k]) && ((j & k) == 0)) dp[i][j] += dp[i-1][k]; } } } cout << dp[n][0] << endl; // 结果为第m+1列没有伸出小矩阵的情况 } }