状态压缩DP——SGU 131

题目链接:

http://acm.sgu.ru/problem.php?contest=0&problem=131


题目大意:

一个N*M的矩形,用格子去将其填满,可以使用的格子有两种。(N<=9 && M<=9)

第1种:1*2的格子

第2种:2*2的格子去掉一个1*1的格子

问一共有多少 种方案。


解题思路:

由于N和M都是小于9的,所以很容易想到状态压缩DP。

状态转移方程:

dp[i][s]+=num[j][s]*dp[i-1][j];

dp[i][s],表示第i行状态为s时的方案数。

第i-1行状态为j,转移到第i行状态为s,由于j—>s的转移方案数不止一种,

所以我们需要用DFS求取j—>s的所有可能。

我的代码能跑46ms,写的比较挫,主要是因为我在DFS的时候枚举很多无效的状态。


源代码:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#include<iostream>
#include<set>
#include<map>
#include<vector>
#include<algorithm>
#define INF 0x3f3f3f3f
using namespace std;
int n,m;
__int64 dp[9][(1<<10)];      //第i行状态为j时的方案数
int cnt;                //总状态的个数
int state;
int ok1(int k)          //判断第1行状态是否可取,只允许出现连续2个的1
{
    int count=0;
    while(k>0)
    {
        if(k%2==1)  count++;
        else
        {
            if(count%2==1)  return 0;
            else    count=0;
        }
        k=k/2;
    }
    if(count%2==1)  return 0;
    return 1;
}
void dfs(int i,int s,int j,int k)
//i表示当前行,s表示当前行的状态,j表示上一行的状态,k表示上一行的第k个格子
//我们现在需要做的就是把上一行的状态j填满
//当前行的第k个格子必定是空的
{
    int pre0,pre1,pre2,t0,t1,t2;
    if(k>=m)        //格子枚举完毕
    {
        if(j==cnt-1)
            dp[i][s]+=dp[i-1][state];
        return;
    }
    pre0=(j&(1<<(k-1)));                    //上一行的第k-1个格子是否为满
    pre1=(j&(1<<k));                        //上一行的第k个格子是否为满
    pre2=(j&(1<<(k+1)));                    //上一行的第k+1个格子是否为满
    t0=(1<<(k-1));
    t1=(1<<k);
    t2=(1<<(k+1));
    if(pre0 || k==0)
    {
        dfs(i,s,j,k+1);                     //不填,这个也有可能导致出现无效状态
    }
    if(pre1==0)
    {
        dfs(i,s|t1,j|t1,k+1);               //填个竖的
    }
    if(pre1 && k<=m-2)
    {
        dfs(i,s|t1|t2,j,k+2);               //填个横的,这个时候有可能出现无效状态
    }
    if(pre1==0 && pre0==0 && k-1>=0)
    {
        dfs(i,s|t1,j|t1|t0,k+1);            //缺左下
    }
    if(pre1==0 && pre2==0 && k<=m-2)
    {
        dfs(i,s|t1,j|t1|t2,k+1);            //缺右下
    }
    if(pre1==0 && k<=m-2)
    {
        dfs(i,s|t1|t2,j|t1,k+2);            //缺右上,这个时候也有可能产生无效状态
    }
    if(pre1 && pre2==0 && k<=m-2)
    {
        dfs(i,s|t1|t2,j|t2,k+2);            //缺左上
    }
    return;
}
void DP()
{
    int i,j,k,t,sum;
    sum=0;
    for(i=1; i<n; i++)  //枚举当前行
    {
        for(j=0; j<cnt; j++) //枚举上一行的状态
        {
            if(dp[i-1][j]!=0)      //说明i-1行状态j是存在的
            {
                state=j;        //将状态j保存起来
                dfs(i,0,j,0);
            }
        }
    }
    printf("%I64d\n",dp[n-1][cnt-1]);
    return;
}
int main()
{
    freopen("in.txt","r",stdin);
    int i,j,k,t;
    while(scanf("%d%d",&n,&m)==2)
    {
        i=max(m,n);
        j=min(m,n);
        n=i,m=j;
        memset(dp,0,sizeof(dp));
        cnt=(1<<m);
        for(i=0; i<cnt; i++)
        {
            if(ok1(i))
                dp[0][i]=1;
        }
        DP();
    }
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值