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 }

 

 

 

你可能感兴趣的:(poj)