铺瓷砖--状压dp

最近学了状压dp,把之前未解决的题目捋一捋。

这是之前的一道题

今天蒜头君装修新家,给家里买了一种 1×2或2×1 的长方形(如图1)新瓷砖。蒜头君是个懂得审美的人,毕竟人生除了金钱,还有诗和远方。

这个时候蒜头君就在想,这种长方形的瓷砖铺到一个 10×10 10 \times 10 10×10 的地面上有多少种方案?(如图2:是 4×44 \times 44×4 地面的一种方案)

图1:

铺瓷砖--状压dp_第1张图片

图2:

铺瓷砖--状压dp_第2张图片

问10×10的地面用2×1的瓷砖铺能有多少方案。

我们直接一般化,有n×m的地面,求有多少种方案。

n×m的地面是有一道例题的  POJ2411,然后附上一篇自己认为还比较详细的题解

https://blog.csdn.net/u014634338/article/details/50015825

但是我也会讲解一点。

思路还是老思路,由前一行来推下一行。

首先我们选好状态的表示方法,1表示影响下一行,0表示不影响,为什么这样表示,因为这样可以很好的表示横着铺和竖着铺两种方案,1就表示这个格子是竖着铺的第一行,0表示这个格子可能是横着铺的中间一个,也可能是竖着铺的第二个格子,反正0就不影响,下一行不用考虑。

这是别人附的一张图,它是用0表示影响的,1表示不影响。

 

然后了解了状态表示之后,我们就可以推导了。

和很多题一样,对于新的一种状态,我们逐位考虑。

分两大类

①某一位是0。

这个上面也说过,它不影响下一行,可能是横这铺的,也可能是竖着铺的其中的第二个。

对于横着铺,我们要考虑旁边的格子是不是0,如果不是,直接就false了,这个状态不成立

对于竖着铺,首先我们判定它是竖着铺的方法是前一行的同一位是1,当前行的这一位是0,只有满足这两个条件就OK了

②第二类是某一位是1

这就说明它一定竖着铺,而且是第一个格子。

那么上一行就不能是1,就这一个条件。

 

由以上的思路,我们可以写出关键的函数。

 

下面是代码

#include 
#include 
#include
#include
#include
#include 
#include 
#define fori(l,r) for( int i = l ; i <= r ; i++ )
#define forj(l,r) for( int j = l ; j <= r ; j++ )
#define fork(l,r) for( int k = l ; k <= r ; k++ )
#define lef rt<<1
#define rig rt<<1|1
#define mid (l+r)>>1
#define mem(a,val) memset(a,val,sizeof a)
typedef long long ll;
using namespace std;
int r,c;
const int maxn = 1<<12;
ll dp[13][maxn];
bool initcheck( int x )     //检查第一行的状态
{
    int cnt = c-1;
    fori(1,c)
    {
        int curdigit = ( x>>cnt )&1;
        if( curdigit == 0 )
        {
            if( i == c || ( ( x>>(cnt-1) )&1 ) == 1 )
                return false;
            i++;
            cnt--;
        }
        cnt--;
    }
    return true;
}
bool judge( int cur,int pre )
{
    int cnt = c-1;
    fori(1,c)
    {
        if( ( cur>>cnt )&1 )   //当前行某一位为 1
        {
            if( ( pre>>cnt )&1 )
                return false;
        }
        else
        {
            if( !( ( pre>>cnt)&1 ) )   //前面一行不影响,那么当前行要横着铺
            {
                if( i == c || ( pre>>(cnt-1)&1 ) || ( cur>>(cnt-1) )&1 )
                    return false;
                i++;        //横着铺有两格,下一个不用判断
                cnt--;
            }
        }
        cnt--;
    }
    return true;
}
int main()
{
    while( scanf("%d %d",&r,&c) == 2 && r )
    {
        if( c > r )
            swap(c,r);
        mem(dp,0);
        int right = 1<

当然,这个代码没加优化,但是也可以过。

其实有很多状态都是多余的。

我们用vector存一下每一行的状态,这样我们推导的时候,直接去vector里面找上一行的状态就行了,就是写起来有点麻烦。

但是优化的时间很多。存1200ms+到300多ms

说到最快的,当然是打表了,就直接跑一下所以情况,存到二维数组里面,直接输出。当然我觉得没必要这样。

#include 
#include 
#include
#include
#include
#include 
#include 
#define fori(l,r) for( int i = l ; i <= r ; i++ )
#define forj(l,r) for( int j = l ; j <= r ; j++ )
#define fork(l,r) for( int k = l ; k <= r ; k++ )
#define lef rt<<1
#define rig rt<<1|1
#define mid (l+r)>>1
#define mem(a,val) memset(a,val,sizeof a)
typedef long long ll;
using namespace std;
int r,c;
const int maxn = 1<<12;
ll dp[13][maxn];
bool vis[13][maxn];
vector v[13];
bool initcheck( int x )     //检查第一行的状态
{
    int cnt = c-1;
    fori(1,c)
    {
        int curdigit = ( x>>cnt )&1;
        if( curdigit == 0 )
        {
            if( i == c || ( ( x>>(cnt-1) )&1 ) == 1 )
                return false;
            i++;
            cnt--;
        }
        cnt--;
    }
    return true;
}
bool judge( int cur,int pre )
{
    int cnt = c-1;
    fori(1,c)
    {
        if( ( cur>>cnt )&1 )   //当前行某一位为 1
        {
            if( ( pre>>cnt )&1 )
                return false;
        }
        else
        {
            if( !( ( pre>>cnt)&1 ) )   //前面一行不影响,那么当前行要横着铺
            {
                if( i == c || ( pre>>(cnt-1)&1 ) || ( cur>>(cnt-1) )&1 )
                    return false;
                i++;        //横着铺有两格,下一个不用判断
                cnt--;
            }
        }
        cnt--;
    }
    return true;
}
int main()
{
    while( scanf("%d %d",&r,&c) == 2 && r )
    {
        if( c > r )
            swap(c,r);
        mem(dp,0);
        mem(vis,false);
        fori(1,r)
            v[i].clear();
        int right = 1<

 

那么对于前面的那道题,直接输入10 10就可以得到答案了。

哎,当时还是太菜

你可能感兴趣的:(蓝桥杯,dp,状压dp)