哈理工第八届程序设计竞赛同步赛(高年级)B题(铺砖问题)解题报告。

题目描述:

小乐乐想要给自己搭建一个积木城堡。

积木城堡我们假设为n*m的平面矩形。

小乐乐现在手里有1*2,2*1两种地砖。

小乐乐想知道自己有多少种组合方案。


输入描述:

第一行输入整数n,m。(1<=n,m<=10)


输出描述:

输出组合方案数。


样例输入

2 3

样例输出

3


解析:本题是动态规划中的状态压缩经典题型,铺砖问题的模板。想解此题需要考虑两个问题,如何表示当前状态,以及如何状态转移。

以下思路参考自:《算法竞赛进阶指南》李煜东

首要解决的问题是如何确定状态。我们可以观察得到,第 i 行的状态(砖块铺法)只与第 i - 1 行的状态有关,于是我们自然的想到以行为状态,向下递推。具体可行性证明如下:

参考上图,可见标号1,2,3。假设我们所处位置为第 i 行第 j 列( i ,j ),那该位置上方块只可能有三种情况(横着放左右俩块一样,都看作1):

    1.如果我们处于1方块上,说明该方块已放置结束,对下一行没有影响,也就是说下一行不需要续接

    2.假设我们处于方块2上,那么我们知道下一行必须再续接一个方块,即( i + 1, j) 上需要再放一个方块来和方块2组成一个竖着放的砖,这时对下一行的状态有影响。

    3.处于方块3时,我们只能放一个方块来续接上一行的方块,即和方块2来组成一个竖着放的砖。此时对下一行无影响。

综上所述,当且仅当当前方块为2时,才对下一行有影响,而我们假设每个砖块都以当前行为起点横着放或竖着放,所以我们只需用两个状态,分别表示该位置对下一行是否有影响。只需要两个状态,自然而然的想到状态压缩。我们假设有m位二进制数,第k位为1时表示第k列对下一行有影响,为0表示没有影响。至此状态表示已解决,接下来需要解决状态转移问题。

设F[ i , j ]表示第 i 行的状态为 j 时,前 i 行所有铺法的总数。j 是用十进制整数记录 m 位二进制数。

第 i - 1 行的状态 k 能转移到第i行的状态 j ,当且仅当:

    1.j & k = 0 。这保证了每个数字1下面必须是数字0,也就是竖着放的砖上半部分在下一行必须要有下半部分。

    2.j 和 k 执行按位或运算结果的二进制表示中,每一段连续的0都必须是偶数个。这个很好理解,2*1的砖横着放,不管放了多少,一定是2的倍数嘛。

以上即为状态转移条件。

代码示例:(优化1:用到的数组只有两行,故可以用滚动数组。优化2:提前计算出每种状态的连续0是否为偶数个,提高可读性和运行速度)

#include<iostream>
using namespace std;
const int MAXN = 12;
int dp[2][1<<MAXN];
bool is_even[1<<MAXN];
void init(int m){
	for(int i = 0;i < 1<<m;i++){
		int cnt = 0;		//当有偶数个0时cnt为0,否则为1。
		int odd = 0; 		//当该状态所有连续0个数都为偶数时,odd为0,否则为1 
		for(int j = 0;j < m;j++)
			if(i >> j & 1)	odd |= cnt,cnt = 0;
			else cnt ^= 1;
		is_even[i] = odd | cnt;
	}
}
int solve(int n,int m){
	init(m);
	dp[0][0] = 1;
	int idx = 1;
	for(int i = 1;i <= n; i++, idx ^= 1)
		for(int j = 0;j < 1<<m ;j++){
			dp[idx][j] = 0;
			for(int k = 0;k < 1<<m ;k++)
				if((j&k) == 0 && !is_even[j|k])
					dp[idx][j] += dp[idx^1][k];
		}
	return dp[idx^1][0];
}
int main(){
	int n,m;
	cin >> n >> m;
	cout << solve(n,m) << endl;
	return 0;
} 

 

posted @ 2018-12-05 13:52  Dr_Lo  阅读(277)  评论(0编辑  收藏  举报