poj 2411 Mondriaan's Dream (状态压缩dp)

版权声明:转载注明下出处就行了。 https://blog.csdn.net/LJD201724114126/article/details/84799199

题目链接:哆啦A梦传送门

题意:给出n*m的方块,让你用1*2的方块去填满它,问:有多少种不同的方案?

题解:

参考链接:https://blog.csdn.net/u014634338/article/details/50015825

看完题解,大概就这几个核心点。

1,我们用二进制来表示横放和竖放,如图所示:

2,我们已经知道横放和竖放的规则,那么我们只需枚举二进制,判断第i+1行是否跟第i行产生不兼容。

首先我们先初始化第1行。

然后我们可以分类下判断兼不兼容的情况:

(i+1,j) (表示第i+1行,j列)

(i+1,j)为1时,(i,j)为0,满足条件(竖放)

                           (i,j) 为1,必须(i+1,j+1)和(i, j+1) 必须为1才满足条件(两个横放)

(i+1,j)为0时,(i,j)为1,满足条件(横放与竖放)

其它情况都为不兼容。

代码:

#include<cstdio>
#include<algorithm>
#include<cstring>

using namespace std;

typedef long long LL;

LL dp[15][1<<12];
int n,m;

///设置初始状态
bool init(int status)
{
    for(int j=0;j<m;) ///前j-1列符合要求,对第j列进行判断
    {
        if(status&(1<<j)) ///第j列为1
        {
            if(j==m-1) return false; ///j为最后一列
            if(status&(1<<(j+1)))///第j列和第j+1都为1,则表示横放,可行,考虑j+2列
                j+=2;
            else   ///第j列为1,j+1列为0,不可行
                return false;
        }
        else j++; ///第j列为0,则为竖放,可行
    }
    return true;
}


///判断上一次的状态和本次状态是否兼容
bool check(int now,int pre)
{
    for(int j=0;j<m;)
    {
        if(now&(1<<j)) ///第i行第j列为1
        {
            if(pre&(1<<j)) ///第i-1行第j列也为1,那么第i行必然是横放
            {
                ///第i行和第i-1行的第j+1列都必须是1,否则是非法的
                if(j==m-1||!(now&(1<<(j+1)))||!(pre&(1<<(j+1))))
                    return false;
                else j+=2;
            }
            else j++; ///第i-1行第j列为0,说明第i行第j列是竖放
        }
        else{ ///第i行第j列为0,那么第i-1行的第j列应该是已经填充了的,必须为1
            if(pre&(1<<j)) j++;
            else return false;
        }
    }
    return true;
}

void solve()
{
    int tot=(1<<m)-1;

    memset(dp,0,sizeof(dp));

    for(int i=0;i<=tot;i++){ ///初始化第1行
        if(init(i))
            dp[1][i]=1;
    }

    for(int i=2;i<=n;i++)
    {
        for(int j=0;j<=tot;j++)  ///第i行的状态
        {
            for(int k=0;k<=tot;k++) ///第i-1行的状态
            {
                if(check(j,k))
                    dp[i][j]+=dp[i-1][k];
            }
        }
    }

    printf("%lld\n",dp[n][tot]);
}

int main()
{

    while(~scanf("%d%d",&n,&m))
    {
        if(n==0&&m==0) break;

        if(n&1&&m&1) { ///都为奇数
            printf("0\n");continue;
        }

        if(m>n) swap(n,m); ///交换下,使得m较小,减少状态量

        solve();
    }
    return 0;
}

总结下:这里用二进制来表示横放与竖放,就用得很巧妙,我们要想要用状态压缩去做题,必须要找到适合题目要求的独特二进制表示,

这里横放用1 1 ,竖放用0|1 ,就是独特的二进制来表示,之后我们就去枚举,看是否冲突。

我的标签:做个有情怀的程序员。

猜你喜欢

转载自blog.csdn.net/LJD201724114126/article/details/84799199