POJ 2411 解题报告

这道题是状态压缩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就好了,即标记本身是合法的就好。

thestoryofsnow2411Accepted308K16MSC++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;
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值