NC51189 Mondriaan's Dream
题目
题目描述
Squares and rectangles fascinated the famous Dutch painter Piet Mondriaan. One night, after producing the drawings in his 'toilet series' (where he had to use his toilet paper to draw on, for all of his paper was filled with squares and rectangles), he dreamt of filling a large rectangle with small rectangles of width 2 and height 1 in varying ways.
Expert as he was in this material, he saw at a glance that he'll need a computer to calculate the number of ways to fill the large rectangle whose dimensions were integer values, as well. Help him, so that his dream won't turn into a nightmare!
输入描述
The input contains several test cases. Each test case is made up of two integer numbers: the height h and the width w of the large rectangle. Input is terminated by h=w=0. Otherwise, 1≤h,w≤111\leq h,w\leq 111≤h,w≤11.
输出描述
For each test case, output the number of different ways the given rectangle can be filled with small rectangles of size 2 times 1. Assume the given large rectangle is oriented, i.e. count symmetrical tilings multiple times.
示例1
输入
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
题解
知识点:状压dp。
一道经典的状压dp,状态设置十分巧妙。考虑一行一行摆,因为要摆满且有两种块,单纯用 \(01\) 表示横着摆竖着摆会出问题,竖着的会影响下一行,横着的要连着放,非常麻烦。考虑设 \(dp[i][st]\) 表示考虑第 \(i\) 行, 且状态是 \(st\) ,其中 \(1\) 代表这格会延长至下一行,\(0\) 代表这格不会延长至下一行。这样,如果上一行状态是 \(st1\) ,这一行状态是 \(st2\) ,\(st1 | st2\) 中 \(1\) 表示这一行哪些格子被竖着的块占据了,两行的 \(1\) 都对应一个竖块,剩下的 \(0\) 则是横的,于是统计连续出现的 \(0\) 是否为奇数,如果是则状态组合 \(st1 | st2\) 不可能发生。
先预处理出状态组合 \(st1|st2\) 是否可能发生,共 \(2^m\) 种可能,记录在 \(vis\) 数组。
接下来枚举行,本层状态和上层状态,满足 j & k || !vis[j | k] == 1
则不合法,否则有转移方程:
时间复杂度 \(O(n \cdot 4^m)\)
空间复杂度 \(O(n \cdot 2^m)\)
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll dp[12][1 << 11];///第 i 行,状态 j,0表示横着放中的一格或者上上层竖着放下来的(即没有往下延伸),1表示竖着放(往下延伸了)
bool vis[1 << 11];
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n, m;
while (cin >> n >> m, n || m) {
for (int i = 0;i < (1 << m);i++) {///这里的状态不一样,1 表示单独存在的,0 表示是连续两个的
vis[i] = 1;
bool ok = 1;
for (int j = 1;j <= m;j++) {
if (((i >> (j - 1)) & 1)) vis[i] &= ok, ok = 1;
else ok ^= 1;
}
vis[i] &= ok;///最后没有1的残留状态转移
}
memset(dp, 0, sizeof(dp));
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 (j & k || !vis[j | k]) continue;
///第一个判断1是否重叠,因为上面1,下面不能放1
///第二个判断,把上面戳下来的一起当作1表示独立一格,记录连在一起的0的个数是否是偶数
dp[i][j] += dp[i - 1][k];
}
}
}
cout << dp[n][0] << '\n';///最终一定是全0,不会往下戳的情况
}
return 0;
}
本文来自博客园,作者:空白菌,转载请注明原文链接:https://www.cnblogs.com/BlankYang/p/16651797.html