pojPOJ 2411--Mondriaan's Dream+状态压缩dp

又是一道经典的状态压缩dp

开始自己想了一下,总是觉得因为这个小矩形可以竖着放导致没法确定状态如何转移(第i行的小矩形如果竖着放,及可能影响i-1行,也有可能影响i+1行);后面看了别人的题解后,才知道原来我们可以固定小矩形竖着放的时候只能向前放,这样第i行的状态就只能影响i-1行了,也就能顺利的写出状态转移方程啦。

设dp[i][j]表示第i行处于状态j的时候,共有多少种放置方法。

dp[i][j]=sum(dp[i-1][k]),其中状态j和k要能共存,并且j和k要使得第i-1行刚好铺满。

然后就是初始化,初始化时我们将第一行有连续偶数个1的状态赋值为1(因为第一行没有上一行,是不可能出现竖放情况的)其余所有状态为0。

这个题目中较难处理的就是状态转移中j和k必须共存,并且j和k要使得第i-1行刚好铺满的这个条件。

代码中是以j状态为主然后去判断k状态,其实也可以反过来的;具体的处理见代码。

当然这个题目如果就这样赤裸裸的交是会超时的,还可以加点优化:

1)如果n*m为奇数,则是不可能拼凑成功的(因为小矩形的面积是偶数)

2)  总是取n,m中小的那个当成m,这样可以减少状态总数。

3)  因为n,m很小,输入的状态中应该有重复的,所以我们可以把每次的答案记录下来,下次遇到相同的输入时直接输出答案。

还有就是注意一下位运算的优先级(多加括号).


代码如下:


#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;

long long dp[12][3300];
long long ans[12][3300];
int n,m;

bool init(int x)
{
    for(int i=0;i<m;)
    {
        if(x&(1<<i))
        {
            if(i==m-1)
                return false;
            if(x&(1<<(i+1)))
                i+=2;
            else
                return false;
        }
        else i++;
    }
    return true;
}

bool check(int x,int y)
{
    //提取x的每一位
     for(int i=0;i<m;)
     {
         if(x&(1<<i))//如果x的某一位为1
         {
             if(y&(1<<i))//同时y的那一位也为1,表明x,y这个位置的砖块都是横放的
             {//判断一个地方的砖块是否是横放时,我们只需要和它右边的位置进行
                 //对比,而不要和左边的进行对比(因为只是一个递推的过程)
                //这样就已经可以保证正确性(假设当前合理,最后推出矛盾)
                 if(i==m-1||!(x&1<<(i+1))||!(y&1<<(i+1)))
                    return false;
                 i+=2;
             }
             else i++;//如果y的对应位是0,则表明x的当前位是竖放的(当然有可能不是竖放的)
                    //如果那样在后面是会出现矛盾的,所以不需在当前进行更复杂的判断
         }
         else //如果x的当前位为0,则y的对应位一定为1,否则返回false
         {
             if(y&(1<<i))
                i++;
             else
                return false;
         }
     }
     return true;
}

int main()
{
    memset(ans,0,sizeof(ans));
    while(scanf("%d%d",&n,&m)&&n)
    {
        if(n*m&1)
        {
            printf("0\n");
            continue;
        }
        if(ans[n][m]||ans[m][n])
        {
            printf("%d\n",ans[n][m]);
            continue;
        }
        if(n<m)
        {
            int t=n;
            n=m;
            m=t;
        }
        int top=(1<<m);
        memset(dp,0,sizeof(dp));
        for(int i=0;i<top;i++)
        {
            if(init(i))
                dp[1][i]=1;
        }
        for(int i=2;i<=n;i++)
            for(int j=0;j<top;j++)
            {
                 for(int d=0;d<top;d++)
                 {
                     if(check(j,d))
                        dp[i][j]+=dp[i-1][d];
                 }
            }
        printf("%lld\n",dp[n][top-1]);
    }
  return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值