动态规划之状态压缩

来源:互联网 发布:云计算服务分类 编辑:程序博客网 时间:2024/05/22 05:15

最近在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 0