POJ 2411 Mondriaan's Dream [经典状态压缩dp]

题意:略。

思路:这一题开始做的时候完全没有思路,便去看了别人的题解。

首先,对于这个题目解法想有一个初步的了解,请看这里:http://www.2cto.com/kf/201208/146894.html

根据这篇讲解,写了一篇扭曲的代码,提交之后TLE。

经过排查分析之后发现,算法的复杂度为O(hw*(2^(2w))),这个复杂度肯定超了。后来进行了优化,如果两种状态可以匹配,就将它们用邻接表(vector实现)存储起来,这样只需一遍预处理,以后直接读取就可以了。

此外,还有两个地方的优化:

1. 如果h*w为奇数,则结果必为0。(每个砖块的面积为2,无法用整数块铺满)

2. 如果h < w, 将两者数值交换。

后面还有一种dfs+dp的解法,我觉得很精巧,在下面重点分析。这里先贴下上面方法的代码。

 1 #include<stdio.h>
 2 #include<iostream>
 3 #include<vector>
 4 #include<string.h>
 5 #include<algorithm>
 6 using namespace std;
 7 long long dp[1<<12][13];
 8 vector<int> ok[1<<12];
 9 int h, w;
10 bool judge(int up, int down)
11 {
12     int u[13], d[13];
13     int now = w;
14     while (now)
15     {
16         u[now] = up % 2;
17         d[now--] = down % 2;
18         up /= 2;
19         down /= 2;
20     }
21     for (int i = 1; i <= w;)
22     {
23         if (!d[i])//该行该位为0
24         {
25             if (!u[i]) return 0;//上一行若也为0,则不合法
26             i++;
27         }
28         else if (!u[i])//该行该位为1,且上一行该位为0
29             i++;
30         else//该行该位为1,且上一行该位也为1
31         {
32             if (i + 1 > w || !d[i+1] || !u[i+1]) return 0;
33             i += 2;
34         }
35     }
36     return 1;
37 }
38 long long getdp()
39 {
40     memset(dp, 0, sizeof(dp));
41     for (int i = 0; i < (1<<w); i++)
42     {
43         ok[i].clear();
44         for (int j = 0; j < (1<<w); j++) if (judge(j, i))
45             ok[i].push_back(j);
46     }
47     for (int i = 0; i < (1 << w); i++)
48         for (int j = 0; j < ok[i].size(); j++) if (ok[i][j] == (1<<w) - 1)
49         dp[i][1] = 1;
50     for (int i = 2; i <= h; i++)
51         for (int j = 0; j < (1 << w); j++)
52             for (int k = 0; k < ok[j].size(); k++)
53                 dp[j][i] += dp[ ok[j][k] ][i-1];
54     return dp[(1<<w)-1][h];
55 }
56 int main()
57 {
58     while (~scanf("%d%d", &h, &w) && h && w)
59     {
60         if ((h * w) % 2)
61         {
62             printf("0\n");
63             continue;
64         }
65         if (h < w) swap(h, w);
66         printf("%lld\n", getdp());
67     }
68     return 0;
69 }

===========分割线===============

dfs的方法:

状态压缩的原则与上一种方法是一样的:如果该位为0,则说明该处为一竖放的砖块,且为该砖块的上半部分;其余为1。

核心部分是dfs的方法。首先,dp[state][row]表示铺到第row行,且该行状态为state时的方法总数,这并没有变。每次枚举状态,与上面不同的是,枚举的是上一层的状态。

上一种方法:

for(i = 2; i <= h; i++)//枚举行数

  for(j = 0; j < (1<<w); j++)//枚举该行的状态

    for(k...)//枚举该行可匹配的上一行状态

dfs版:

for(i = 2; i <= h; i++)//枚举行数

 

  for(j = 0; j < (1<<w); j++)//枚举上一行的状态

 

    if(...) dfs(...)//如果上一行该状态方法数不为0,则dfs遍历该行可行状态

遍历的方法很巧妙:假设所枚举到的上一行状态为s,则将s每一位都取反便是该行的一种可行状态。因为,如果s的某一位是0,说明这是一竖放砖块的上半部分,取反后恰好就是下半部分对应的1;如果s某一位是1,取反后是0,这当然也是可行的。

又由于当s某一位是1时,下一行对应的位可以是0或者1(若是1则必然是横放的,需要连续两位状态都是1)。所以在dfs的过程中需要遍历所有两个0相邻的情况,将他们置为1。当dfs的下标pos到达最后一位(即pos=w)时,说明该状态是可行的,就将该状态的dp值加上上一行状态s的dp值。因此,每次dfs之前都需要暂时存储下上一行状态s的dp值。

这种方法代码既短又巧妙,让人佩服啊。

 

 1 #include<stdio.h>
 2 #include<algorithm>
 3 #include<string.h>
 4 using namespace std;
 5 long long dp[1<<12][13], tem, h, w;
 6 void dfs(int row,int state,int pos)
 7 {
 8     if (pos == w)
 9     {
10         dp[state][row] += tem;
11         return;
12     }
13     dfs(row, state, pos + 1);
14     if (pos <= w - 2 && !(state & (1<<pos)) && !(state & (1<<(pos + 1))))
15         dfs(row, state | 1<<pos | 1<<(pos+1), pos + 2);
16 }
17 int main()
18 {
19     while (~scanf("%d%d", &h, &w) && h && w)
20     {
21         if (h * w % 2)
22         {
23             printf("0\n");
24             continue;
25         }
26         if (h < w) swap(h, w);
27         memset(dp, 0, sizeof(dp));
28         tem = 1;
29         dfs(1, 0, 0);
30         for (int i = 2; i <= h; i++)
31             for (int j = 0; j < (1<<w); j++) if (dp[j][i-1])
32             {
33                 tem = dp[j][i-1];
34                 dfs(i, ~j & ((1<<w) - 1), 0);
35             }
36         printf("%lld\n", dp[(1<<w)-1][h]);
37     }
38     return 0;
39 }

 

 

 

posted @ 2013-08-20 11:17  fenshen371  阅读(209)  评论(0编辑  收藏  举报