从hihoCoder的一道题学习状态压缩+dp

首先,给上题目链接:http://hihocoder.com/problemset/problem/1048 ,简要描述如下:

有1个N*M的盘子,要装2*1的蛋糕,问有多少种方法?(结果对1000000007取余,其中2≤N≤1000,3≤M≤5)

最简单的例子如下:

下面我们来分析:

这个题目类属于状态压缩DP,对于状态压缩DP,其实最简单的理解就是把状态用比特位的形式表示出来,我们会在下面用例子来说明。

假如现在我们在铺砖 位置(i,j), 并且假设之前的位置已经铺设好的了,在这个位置,我们的选择:

 

1. 不用铺砖了,可能在(i-1, j)的时刻已经被竖着铺上了,然后考虑的是(i, j+1)

2. 横铺砖,将(i, j+1)也铺上了,然后考虑的是(i, j+2)。

3. 竖着铺砖,(将i,j)和(i+1,j)铺上一个竖立的转头。

 

所以我们如下翻译我们的选择,在位置(i, j) 如果我们选择横着贴砖,那么将(i, j), (i, j+1)都填写成1如果竖着贴砖,我们将(i,j)填写成0(i+1, j)填写成1.

 

问题1:为什么要这么计数呢,我觉得应该这样理解:

 

1. 在横着贴砖的时候,(i, j), (i, j+1) 都是1,这个值其实对下一行如何选择没有影响。

 

2. 竖着贴砖的第二个,我们也选择了1, 因为这个砖头结束了,对下一行如何选择依然没有影响。

 

3. 而竖着的第一个砖头,这个砖头是对下面有影响的,如果(i,j)是0,那么(i+1,j)只有是1的情况下才能满足条件。

 即当设为1表示对下一行没有任何影响了。

 

问题2:如何判断当前状态与上一行的状态是否兼容

其实我们在上面已经基本给出分析, 如果我们现在铺设 (i,x) x这里表示第i行,第x列

 

1. 如果值 i  行,j 在x位上的值是0, 那么第 i-1行,j的值在x位上一定是1。因为不可能在同一列相邻的位置铺两个竖着的 第一个,如果满足下一步测试的是(i, x+1), 否则直接返回不兼容。

 

2. 如果值 i  行,j在x位置的值是1 .

 

{


            那么有可能有两种情况:

 

            1. (i-1, x)是0, 这个时候一定是竖着铺设了,下一步检测的是(i, x + 1)


            2. (i-1, x) 是1, 如果是这样的话,那么(i, x)一定是要选择横着铺了,那么(i,x+1)也一定是1,并且(i-1,x + 1)一定是1(如果是0,就是竖着铺了),如果不满足就返回不兼容,满足条件 就测试(i,x + 2)

}

 

对于第一行的兼容性,我们要做一下特别的分析,在第一行中,要么放0, 要么放1。

 

加入当前测试的是 DP(0, j)的第 x的比特位,即第0行,x列

 

1. 如果x是1,那么 x + 1 也一定是1,然后测试到 x + 2

 

2. 如果x是0, 那么直接测试下一个 x + 1

特别注意:这里的判断的(i,x)一定不是由(i,x-1)位横着铺砖过来的,否则直接x=x+2,就不会来判断(i,x)位了。

 

问题3:为什么可以使用动态规划算法来解决这个问题?

这就得从动态规划的特性上去找:

(1)最优子结构

 用F[i][j]表示第i行j状态铺砖块的方案数,一定等于i-1行所有的能与状态j兼容的状态k的方案的总和

(2)重复子问题

求F[i][j]即第i行的每一个状态一定要用到第i-1行的各个状态。

 

问题4从状态压缩的特点来看,这个算法适用的题目符合以下的条件:

1.解法需要保存一定的状态数据(表示一种状态的一个数据值),每个状态数据通常情况下是可以通过二进制来表示的。这就要求状态数据的每个单元只有两种状态,比如说棋盘上的格子,放棋子或者不放,或者是硬币的正反两面。这样用 0 或者 1 来表示状态数据的每个单元,而整个状态数据就是一个一串 0 和 1 组成的二进制数

2.解法需要将状态数据实现为一个基本数据类型,比如 int long等等,即所谓的状态压缩。状态压缩的目的一方面是缩小了数据存储的空间,另一方面是在状态对比和状态整体处理时能够提高效率。这样就要求状态数据中的单元个数不能太大,比如用 int 来表示一个状态的时候,状态的单元个数不能超过 32(32 位的机器)。

 

//注:j&(1<<i)的值 可以用二维数组保存起来 
#include <stdio.h>
#include <string.h> 
#define P 1000000007
#define SWAP(a,b) a=b-a+(b=a)
#define FALSE 0
#define TRUE 1

int TestFirstLine(int j,int m){
	int i=0;
	while(i<m){
		if((j & (1<<i)) == 0) //注意打括号 
			i++;
		else if(i==m-1 || !(j & (1<<(i+1))))
			return FALSE;
		else i+=2; 
	}	
	return TRUE;
} 

int TestCompatible(int j,int k,int m){
	int i=0;
	while(i<m){
		if((j & (1<<i)) == 0){
			if((k & (1<<i)) == 0)
				return FALSE;
			else i++; 
		}
		else{
			if((k & (1<<i)) == 0) i++;
			else if(i==m-1 || !((j & (1<<(i+1))) && (k & (1<<(i+1)))))
				return FALSE;
			else i+=2; 
		}
	}
	return TRUE;
}

int main(){
	int n,m,allStates,i,j,k;
	long long dp[1000][32];
	scanf("%d%d",&n,&m);
	if(n<m) SWAP(n,m);//降低时间复杂度 
	allStates=1<<m;
	memset(dp,0,sizeof(dp));
		
	for(j=0;j<allStates;j++)
		if(TestFirstLine(j,m))
			dp[0][j]=1;
			
	for(i=1;i<n;i++)
		for(j=0;j<allStates;j++)
			for(k=0;k<allStates;k++)
				if(TestCompatible(j,k,m)){
					dp[i][j]+=dp[i-1][k];
					dp[i][j]%=P; 
				}
				
	printf("%lld\n",dp[n-1][allStates-1]);//最后一行均为1,所以可输出dp[n-1]的任意一个分量 
	return 0;
}

 

参照:http://blog.csdn.net/lu597203933/article/details/44137277

 

 

 

posted @ 2017-03-14 14:58  我在这儿  阅读(307)  评论(0编辑  收藏  举报