POJ 2411(轮廓线dp or 状态压缩dp)

题目:click
题意:给定一个长,宽为n,m的矩阵,放满1*2的矩形小块问有多少种方法填满。
1:轮廓dp
范围很小,轮廓dp可以做。

k5 k4 k3
k2 k1

状态改写为如上k5k4k3k2k1去表示,1表示这个格子已经放了,0表示没放。(代码中的状态表示也是这个顺序)
什么是轮廓线?

在这里插入图片描述
图中的红色线就是边缘线,由此来进行状态转移。我们对每一个格子进行状态的分析,我们从左上角开始考虑放置,从上往下,从左往右,每个格子有三种情况:
A:不放矩形
B:横着放
C:竖着放
A:什么时候能够不放,那么必定它上头的方格状态是1,也就是被利用了,这会儿不填,下一行的状态会影响到它不填也可以,但是上头必须被填。
B:我需要横着放,第一列肯定不行,那么我上头的格子一定要被利用否则我优先竖着放要填满,前一个格子状态是0,一旦前面格子状态是0,我就需要去填满。
C:竖着放,第一行肯定不行,需要上一个格子状态为0,一旦上头格子为0,我就需要去填满,不用考虑前面格子是因为后面的状态还有机会填满前面的格子。
我们还是要按满足填满要求的,所以状态这么转移。
dp[i][j][k]:表示第i行第j个格子 轮廓线的状态为k时的方法数,转移不过来的就是不能成功放满的即为0。
两者状态只与上一层的轮廓线产生的状态有关,滚动数组。
具体见代码。

#include<cmath>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cstdlib>
#include<istream>
#include<vector>
#include<stack>
#include<set>
#include<map>
#include<algorithm>
#include<queue>
#define inf 0x3f3f3f3f
#define MAX_len 50100*4
using namespace std;
typedef long long ll;
const int NUM=(1<<12);
ll dp[2][NUM];
ll n,m;
int main()
{
    ll i,j,k;
    while(~scanf("%lld %lld",&n,&m)&&(n||m))
    {
        if(m>n)//做一点小优化
      	   swap(n,m);
        memset(dp,0,sizeof(dp));
        dp[0][(1<<m)-1]=1;//开始把上面都看作填满的状态就一种方法
        int hh=0;
        for(i=0;i<n;i++)
        {
            for(j=0;j<m;j++)
            {
                memset(dp[(hh+1)&1],0,sizeof(dp[(hh+1)&1]));
                for(k=0;k<(1<<m);k++)
                {
                    if(k&(1<<(m-1)))//不放
                    {
                        dp[(hh+1)&1][(k<<1)&((1<<m)-1)]+=dp[hh&1][k];
                    }

                    if(j&&!(k&1)&&(k&(1<<(m-1))))//横着放
                    {
                        dp[(hh+1)&1][((k<<1)|3)&((1<<m)-1)]+=dp[hh&1][k];
                    }

                    if(i&&!(k&(1<<(m-1))))//竖着放
                    {
                        dp[(hh+1)&1][((k<<1)|1)&((1<<m)-1)]+=dp[hh&1][k];
                    }
                }
                hh++;
            }
        }
        printf("%lld\n",dp[hh&1][(1<<m)-1]);
    }
    return 0;
}

2:状压dp
与轮廓dp不同的是,这里的状态dp是直接判断一行一行的状态,而轮廓dp是从格点出发的。
可以分析一下,如果上一层状态有0,那么下一层一定要有个1去跟0匹配,因为我是要去填满的,这一行不填的话那么下一行就没有机会去弥补了。
所以上0下1其实就是竖着放。
但是我们这里需要判断一下,当上面的格子是1的时候,下面的格子可以是0或者1,如果下面的格子是1那么,下面的格子为1的连续区间的长度一定是一个偶数才符合条件,也就是全部横放了,与运算一下判断。
具体见代码。(其实可以先打表用一个数组去直接O(1)的求出状态是否符合,这样会更快一点,有点懒就直接加在里面了。。。应该是没什么极限数据吧)

#include<cmath>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cstdlib>
#include<istream>
#include<vector>
#include<stack>
#include<set>
#include<map>
#include<algorithm>
#include<queue>
#define inf 0x3f3f3f3f
#define MAX_len 50100*4
using namespace std;
typedef long long ll;
const int NUM=(1<<12);
ll dp[2][NUM];
ll n,m;
bool check(ll temp)
{
    ll cnt=0;
    while(temp)
    {
        if(temp&1)
        {
            cnt++;
        }
        else
        {
            if(cnt&1)
                return false;
            else
                cnt=0;
        }
        temp>>=1;
    }
    if(cnt&1)
        return false;
    else
        return true;
}
bool check1(ll x,ll y)
{
    for(ll i=0;i<m;i++)
    {
        if(((1<<i)&y)==0)
        {
            if((1<<i)&x)
            {
                continue;
            }
            else
                return false;
        }
    }
    return true;
}
int main()
{
    ll i,j,k;
    while(~scanf("%lld %lld",&n,&m)&&(n||m))
    {

        if(m>n)//稍微优化一下
            swap(n,m);
        memset(dp,0,sizeof(dp));
        for(i=0;i<(1<<m);i++)
        {
            if(check(i))
                dp[0][i]=1;
            else
                dp[0][i]=0;
        }
        int hh=0;
        for(i=1;i<n;i++)
        {
            memset(dp[(hh+1)&1],0,sizeof(dp[(hh+1)&1]));
            for(j=0;j<(1<<m);j++)//这一层的状态
            {
                for(k=0;k<(1<<m);k++)//上一层的状态
                {
                    if(dp[hh&1][k]==0)
                        continue;
                    if(check(j&k)&&check1(j,k))
                    {
                        dp[(hh+1)&1][j]+=dp[hh&1][k];
                    }
                }
            }
            hh++;
        }
        printf("%lld\n",dp[hh&1][(1<<m)-1]);
    }
    return 0;
}

发布了72 篇原创文章 · 获赞 19 · 访问量 7494

猜你喜欢

转载自blog.csdn.net/weixin_43958964/article/details/105074734
今日推荐