这道题是状态压缩DP。之前应该做过一次类似的,但这道题比较难些。花了比较久理解状态压缩DP。周伟的论文《状态压缩》是最好的资料。
这道题是论文中的一道例题。论文中提到,由于合法状态特别多,所以这里对每一行都跑一次DFS,而不是提前保存下来。这样可以节省空间,时间上也没多少影响。事实也确实如此,16ms就过了。
整体过程是从上到下一行行往下填,DP[r][s]表示,第r行状态为s时填充方法有多少。我们要求的是DP[h - 1][(1 << w) - 1]的值,因为我们需要保证最后一行全都填充。
我们可以讲横着(horizontal)放置的小方块表示为11。即当前行的当前列和下一列填1。而把竖着(vertical)放置的小方块表示为01。即上一行的当前列为0,当前行的当前列为1。最后一行全都填充的意思是最后一行的每一列都不能是竖条的起点(0),即必须全为1,所以我们有(1<<w) - 1。
当前行的状态只和前一行的状态有关,反过来讲,如果当前行的状态确定,上一行的状态也确定了。反过来想比较容易填充 DP[r][s]。
对每一行,我们从左到右一列列填充。具体来说,
1.如果我们在当前行r的列c和c+1放一个横条,那么上一行(r -1)的c和c +1也必须为1(一个横条或者两个竖条的终点,或者一半一半,总之必须为11)。即有:
dfs(r, c + 2, (s1 << 2) | 3, (s2 << 2) | 3);
2.如果我们将当前行r的列c作为竖条的终点,那么上一行(r -1)的当前列c一定是竖条的起点(0)。即有:
dfs(r, c + 1, (s1 << 1) | 1 , s2 << 1);
3.如果我们将当前行r的列c作为竖条的起点,那么上一行(r -1)的当前列c一定是已经填充好了,即必须为1(无论是横条还是竖条的终点,总之已经“完整”地填充了)。即有:
dfs(r, c + 1, s1 << 1 , (s2 << 1) | 1);
当前行的状态s1是所有上一行可能的状态s2的填充方法之和,即:dp[r][s1] += dp[r - 1][s2];
还有一个值得注意的地方是第一行(r = 0)。第一行不能作为竖条的终点,即不能存在上述情况2.并且,因为不存在前一行,dp[r][s1] = 1就好了,即标记本身是合法的就好。
thestoryofsnow | 2411 | Accepted | 308K | 16MS | C++ | 1901B |
/*
ID: thestor1
LANG: C++
TASK: poj2411
*/
#include <iostream>
#include <fstream>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <limits>
#include <string>
#include <vector>
#include <list>
#include <set>
#include <map>
#include <queue>
#include <stack>
#include <algorithm>
#include <cassert>
using namespace std;
const int MAXH = 11;
const int MAXW = 11;
const int MAXS = 1 << MAXW;
int h, w;
long long dp[MAXH][MAXS];
// horizontal: 1 1
// vertical : 0 1
// the last row should be all 1
// s1 is current row status,
// s2 is previous(!!!) row status
// according to Zhou Wei's paper,
// the valid status is plenty
// so we run it every row to save space
void dfs(int r, int c, int s1, int s2)
{
if (c >= w)
{
// does not cross border
if (c == w)
{
if (r == 0)
{
dp[r][s1] = 1;
}
else
{
dp[r][s1] += dp[r - 1][s2];
}
}
return;
}
// if current row is horizontal, then the previous row should be horizontal as well
// (or both columns are the end of vertical which are the same, '11')
dfs(r, c + 2, (s1 << 2) | 3, (s2 << 2) | 3);
// current row is the end of vertical, then the previous row should be the start
// the first row cannot be the end of vertical
if (r != 0)
{
dfs(r, c + 1, (s1 << 1) | 1 , s2 << 1);
}
// current row is the start of vertical,
// then the previous row should be the end
// (or part of horizontal, in both cases, '1')
dfs(r, c + 1, s1 << 1 , (s2 << 1) | 1);
}
int main()
{
while (scanf("%d%d", &h, &w) > 0 && h)
{
// rotate the rectangle to make the width smaller
if (h < w)
{
int t = h;
h = w;
w = t;
}
for (int r = 0; r < h; ++r)
{
for (int s = 0; s < (1 << w); ++s)
{
dp[r][s] = 0;
}
}
for (int r = 0; r < h; ++r)
{
dfs(r, 0, 0, 0);
}
// the last row should be all 1
printf("%lld\n", dp[h - 1][(1 << w) - 1]);
}
return 0;
}