1. 程式人生 > >poj 2411 Mondriaan's Dream (狀態壓縮dp)

poj 2411 Mondriaan's Dream (狀態壓縮dp)

題目連結:哆啦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 ,就是獨特的二進位制來表示,之後我們就去列舉,看是否衝突。

 

我的標籤:做個有情懷的程式設計師。