动态规划之状态压缩

最近在hihocoder上做的一道题,一下是原题目以及解题过程。

时间限制: 10000ms
单点时限: 1000ms
内存限制: 256MB
描述

历经千辛万苦,小Hi和小Ho终于到达了举办美食节的城市!虽然人山人海,但小Hi和小Ho仍然抑制不住兴奋之情,他们放下行李便投入到了美食节的活动当中。美食节的各个摊位上各自有着非常多的有意思的小游戏,其中一个便是这样子的:

小Hi和小Ho领到了一个大小为N*M的长方形盘子,他们可以用这个盒子来装一些大小为2*1的蛋糕。但是根据要求,他们一定要将这个盘子装的满满的,一点缝隙也不能留下来,才能够将这些蛋糕带走。

这么简单的问题自然难不倒小Hi和小Ho,于是他们很快的就拿着蛋糕离开了~

但小Ho却不只满足于此,于是他提出了一个问题——他们有多少种方案来装满这个N*M的盘子呢?

值得注意的是,这个长方形盘子的上下左右是有区别的,如在N=4, M=3的时候,下面的两种方案被视为不同的两种方案哦!


原题目见: http://hihocoder.com/contest/hiho9/problem/1 点击打开链接

解题过程:

hihocoder上的题目适合初学者做,因为他有很详细的解题思路的阐述。虽然看了提示,能基本上懂她的算法到底是怎么个原理。但是具体怎么实现还是很困扰。于是我先尝试了递归的方法,代码如下:

<span style="font-size:18px;">#include<stdio.h>
#include<string.h>
#include<math.h>

int mis(int j,int m,int st){
	if((st>>j-1)&&1)return 1;
	}	
int rightbank(int j,int m,int st1){
	if(j==m)return 0;
	if(!((st1>>j)&1))return 1;
	else return 0;
	}	
long long sum(int i,int j,int n,int m,int st1,int st2){
	//printf("%d %d %d %d %d %d\n",i,j,n,m,st1,st2 );
	if(i==n+1)
	{
		if(!st1){
			//printf("11\n");
			return 1;
			}
		else 
		{
			//printf("00\n");
			return 0;
		}
	}
	if(j>m)
	{
	//	printf(">j\n");
		return sum(i+1,1,n,m,st2,0)%1000000007;
	}
	if(mis(j,m,st1))
	{
		//printf("mis\n");
		return sum(i,j+1,n,m,st1,st2)%1000000007;
	}
	else if(rightbank(j,m,st1))
	{		
	//	printf("ri\n");
		return sum(i,j+2,n,m,st1+(1<<(j-1))+(1<<j),st2)+sum(i,j+1,n,m,st1+(1<<(j-1)),st2+(1<<(j-1)))%1000000007;	
	}
	else 
	{
		//printf("do\n");
		return sum(i,j+1,n,m,st1+(1<<(j-1)),st2+(1<<(j-1)))%1000000007;
	}
}
int main(){
	int n,m;
	scanf("%d %d",&n,&m);
	//printf("haha");
	printf("%I64d",sum(1,1,n,m,0,0));
	return 0;
}</span>
<span style="font-size:24px;">代码的想法很简单,根据测试,代码也在输入较小的情况下完成任务(n至少14以下,可这他喵的也太小了。)</span>
<span style="font-size:24px;">
</span>
<span style="font-size:24px;">然后又看看了提示中给的几个转移方程,彻底搞懂每个转移方程的含义后,通过四维数组的方法,写出了下面的代码:</span>
<pre name="code" class="cpp"><span style="font-size:18px;">#include<stdio.h>
#include<string.h>
#include<math.h>
long long f[1010][6][32][32];
int main(){
	int n, m, i,j,st1,st2,pj,qj,pj1;
	memset(f,0,sizeof(f));
	scanf("%d%d",&n,&m);
	f[n+1][1][0][0]=1;
	for(i=n;i>0;i--)
		for(j=m;j>0;j--)
			for(st1=(1<<m)-1;st1>-1;st1--)
				for(st2=(1<<m)-1;st2>-1;st2--)
				{
					pj=(st1>>(j-1))&1;
					pj1=(st1>>j)&1;
					qj=(st2>>(j-1))&1;
				    if(pj==1&&j==m)
						f[i][j][st1][st2]=f[i+1][1][st2][0];
					else if(pj==1&&j<m)
						f[i][j][st1][st2]=f[i][j+1][st1][st2];
					else if(pj==0&&(j==m||pj1==1)&&(i==n||qj==1))
						f[i][j][st1][st2]=0;
					else if(pj==0&&j<m&&pj1==0&&(i==n||qj==1))
						f[i][j][st1][st2]=f[i][j][st1+(1<<(j-1))+(1<<j)][st2];
					else if(pj==0&&(j==m||pj1==1)&&i<n&&qj==0)
						f[i][j][st1][st2]=f[i][j][st1+(1<<(j-1))][st2+(1<<(j-1))];
					else if(pj==0&&j<m&&pj1==0&&i<n&&qj==0)
						f[i][j][st1][st2]=(f[i][j][st1+(1<<(j-1))+(1<<j)][st2]+f[i][j][st1+(1<<(j-1))][st2+(1<<(j-1))])%1000000007;
				}
				printf("%lld",f[1][1][0][0]%1000000007);
	            return 0;
}</span>


 

此时代码已经完全能满足题目的时间和空间要求了。但其实还不够好。空间占用达到49MB;虽然题目没做这方面的要求,但是其实有很大的改进空间,因为每次迭代仅仅需要的是上次计算的结果,于是就有了下面最终版本的代码

<span style="font-size:18px;">#include<stdio.h>
#include<string.h>
#include<math.h>
long long f[2][6][32][32];
int main(){
	
	int n, m, i,j,st1,st2,pj,qj,pj1;
	memset(f,0,sizeof(f));
	scanf("%d%d",&n,&m);
	f[1][1][0][0]=1;
	for(i=n;i>0;i--)
	{
		for(j=m;j>0;j--)
			for(st1=(1<<m)-1;st1>-1;st1--)
				for(st2=(1<<m)-1;st2>-1;st2--)
				{
					pj=(st1>>(j-1))&1;
					pj1=(st1>>j)&1;
					qj=(st2>>(j-1))&1;
					//if(st1<pow(2,j-1)-1)
					//	f[i][j][st1][st2]=0;
					 if(pj==1&&j==m)
						f[0][j][st1][st2]=f[1][1][st2][0];
					else if(pj==1&&j<m)
						f[0][j][st1][st2]=f[0][j+1][st1][st2];
					else if(pj==0&&(j==m||pj1==1)&&(i==n||qj==1))
						f[0][j][st1][st2]=0;
					else if(pj==0&&j<m&&pj1==0&&(i==n||qj==1))
						f[0][j][st1][st2]=f[0][j][st1+(1<<(j-1))+(1<<j)][st2];
					else if(pj==0&&(j==m||pj1==1)&&i<n&&qj==0)
						f[0][j][st1][st2]=f[0][j][st1+(1<<(j-1))][st2+(1<<(j-1))];
					else if(pj==0&&j<m&&pj1==0&&i<n&&qj==0)
						f[0][j][st1][st2]=(f[0][j][st1+(1<<(j-1))+(1<<j)][st2]+f[0][j][st1+(1<<(j-1))][st2+(1<<(j-1))])%1000000007;
				}
					
	memcpy(f[1],f[0],sizeof(f[1]));
	//printf("%I64d",f[1][1][0][0]%1000000007);
	memset(f[0],0,sizeof(f[0]));
	}
				
				
	printf("%lld",f[1][1][0][0]%1000000007);
	return 0;
}</span>


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值