POJ241 Mondriaan's Dream(状态压缩DP)

人生第一道状态压缩DP,我这个弱渣想了N天终于弄懂了◑﹏◐

题目大意:求1*2的地板填满n*m的砖块有多少种不同的方法

思路:位运算+DFS+状态压缩。
当高度和宽度都为奇数时答案为0。
对于每一个位置,我们有三种放置方法:
1. 竖直放置
2. 水平放置
3. 不放置
d为当前列号 ,初始化d, now, pre都为0;对应以上三种放置方法,now, pre的调整为:
1. d = d + 1, now << 1 | 1, pre << 1;
2. d = d + 2, now << 2 | 3, pre << 2 | 3;
3. d = d + 1, now << 1, pre << 1 | 1;
先就第一种情况解释一下,另外的两种情况可以类推
now<<1|1即为把s1的二进制表示后面加上一个1,对就于本题来说就是(d+1)列上放
置,pre<<1即为把s2的二进制表示后面加上一个0,对于本题来说就是(d+1)列上不放置。
now对应于本行的状态,pre对应于上一行的状态,能竖直放置意味着上一行的(d+1)列是空着的,因此此时上一行的状态为pre<<1,同时竖
置放置了之后,则本行(d+1)行放置了东西,状态于是变为now<<1|1;
当d = w时保存状态
对于初始时的f值,可以假设第0行全满,第一行只有两种放法:
1. 水平放置 d = d + 2, s << 2 | 3;
2. 不放置 d = d + 1, s << 1;
最后只要判断h行是否全为1就行了,只取最后一行全为1的答案,也就是(1<

#include <iostream>
#include <string>
#include <cstring>
#include <stdio.h>
#include <cstdlib>
#include <map>
#include <set>
#include <vector>
#include <queue>
#include <functional>
#include <algorithm>
#include <cmath>
#include <assert.h>
#include <stack>
using namespace std;
#define maxn 1<<12
int h,w,p,q;
long long dp[2][maxn];

void dfs(int col,int now,int pre) //now当前行状态,pre前一行状态
{
    if(col==w)  //col已经移到当前行最左边一列
    {
        dp[q][now]+=dp[p][pre];
        return;
    }
    if(col+1<=w)
    {
        dfs(col+1,now<<1,pre<<1|1);   //不放 ,故而pre<<1|1,即将当前行的下一列由0变1
            //当前行在下一个dfs变成前一行,即pre
        dfs(col+1,now<<1|1,pre<<1);    //竖放
        //当下一行行的下一列由0变1(now<<1|1)
    }
    if(col+2<=w)
        dfs(col+2,now<<2|3,pre<<2|3);  //横放
    //3二进制为100,或运算将当前行的下一行同一列由0变成1
}

void solve()
{
    int i;
    if((( w * h ) & 1) )  //&1相当于%2,但是与运算更快
    {
        printf("0\n");
        return;
    }
    memset(dp,0,sizeof(dp));
    p=0;
    dp[0][(1<<w)-1]=1;
    for(i=1;i<=h;i++)
    {
        q=p^1;    //滚动数组,p,q轮替交换,p是上一行,q是当前行
        dfs(0,0,0);
        memset(dp[p],0,sizeof(dp[0]));
        p=q;
    }
    printf("%lld\n",dp[q][(1<<w)-1]);
}

int main()
{
    while(~scanf("%d%d",&h,&w)&&(w||h))
    {
        if(w>h)swap(w,h);
        solve();
    }
    return 0;
}

你可能感兴趣的:(POJ241 Mondriaan's Dream(状态压缩DP))