POJ-2411 Mondriaan's Dream 状态压缩+DP
还记得杭电的“骨牌铺方格”吗,这题可以说是它的强化板了。但是思路是一样的,就是通过得到前面的解来递推后面的结果。
由于是一个较大的平面,较之杭电那题在行数和列数上都有了较多的组合情况,因此状态压缩有了用武之地。
这题的一个难点就是如何用0,1来表示摆放的规律,参看别人的想法后,找到的这种方法虽然时间开销相对大了点,但是思路非常好理解。
对于每一个格子,只有0,1两种状态,0代表没有放置,1代表放置。对于每一行的状态,1的含义会因为上一行的对应项而不同,如果与之对应的上一行的列为0的话,那么这个1代表这里放置了一个竖直的块,如果上一列是1的话那么两行都必须是横着放置的。
2411 | Accepted | 328K | 1844MS | C++ | 2143B |
定义了这些后,就是写代码了,本来在judge函数中想通过位运算来解决,但是采用这样的0,1定义对位运算不利,这也是为什么时间开销比较大的原因,在judge时采用了传统的解压方式......逐个判定。
1. 初始化
2. dfs出第一行的状态,注意第一行的1必须要相邻,而且必须是偶数个,能够想清楚吗?
3. DP更新
代码如下:
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <iostream>
using namespace std;
int H, W, cnt;
unsigned int MaxN;
long long dp[15][1050];
void print(int x, int L)
{
if (x != 0) {
print(x >> 1, --L);
printf("%d", x % 2);
}
else {
while (L--)
printf("0");
}
}
inline int lowbit(int x)
{
return x & -x;
}
void dfs(int p, int num, int total)
{
if (p == W){
if (!(total & 1)){
dp[1][num] = 1;
}
return;
}
else{
if (total & 1)
dfs(p+1, num<<1|1, total+1);
else{
dfs(p+1, num<<1, total);
dfs(p+1, num<<1|1, total+1);
}
}
}
int judge(int x, int y)
{
for (int i = 0; i < W; ++i)
{
if (x>>i & 1) {// 第i位为奇数
if (y>>i & 1) {
if (i == W-1)
return 0;
else if (!(x>>(i+1)&1 && y>>(i+1)&1)){
return 0;
}
else ++i;
}
}
else if (!(y>>i & 1)){
return 0;
}
}
return 1;
}
void DP()
{
for (int i = 2; i <= H; ++i) { // 针对行的一个遍历
for (int j = 0; j <= MaxN; ++j) { // 针对状态的一个遍历
for (int k = 0; k <= MaxN; ++k) { // 针对状态的一个遍历
if (judge(k, j)) {
dp[i][j] += dp[i-1][k];
}
}
}
}
}
void init()
{
memset(dp, 0, sizeof (dp));
if (!(H & 1) && !(W & 1)) { // 将状态变小
if (H < W) {
H = H + W;
W = H - W;
H = H - W;
}
}
else if (!(H & 1)) {
H = H + W;
W = H - W;
H = H - W;
}
MaxN = (1<<W)-1; // 所有的可能性
dfs(0, 0, 0);
}
int main()
{
while (scanf("%d %d", &H, &W), H|W) {
if (W & 1 && H & 1) {
puts("0");
continue;
}
init();
DP();
cout << dp[H][MaxN] << endl;
}
return 0;
}