1. 程式人生 > >POJ 2411 Mondriaan's Dream 【狀壓Dp】 By cellur925

POJ 2411 Mondriaan's Dream 【狀壓Dp】 By cellur925

題目傳送門

這道題暑假做的時候太模糊了,以前的那篇題解大家就別看了==。今天再複習狀壓感覺自己當時在寫些什麼鴨...。

題目大意:給你一個\(n\)*\(m\)的棋盤和許多\(1*2\)的骨牌,骨牌可以豎放或橫放,問有多少種方案將骨牌鋪滿。

設計狀態,\(f[i][j]\)表示當前在第\(i\)行,之前的所有行都已經鋪滿,當前行的狀態為\(j\)的方案數。如果我們對01串的定義仍確定為1為放了0為沒放,那麼真的對嘛?

好像不行,存出不了那麼多資訊。我們試著改變0和1的含義。因為骨牌要麼是橫放要麼是豎放,那麼我們設第\(k\)位為1是一個豎矩形的上面一半,為0代表其他情況。

考慮轉移,第\(i-1\)

行能轉移到第\(i\)行當且僅當①這一行狀態與上一行狀態與運算為0.(保證了每個數字為1的位下面一定為0,以繼續補全)。②兩行狀態或運算後的二進位制表示,連續的0長度必須為偶數,表示橫放。

於是我們可以預處理出所有橫放的情況,再進行\(O(4^m*n)\)的轉移。目標狀態\(f[n][0]\)

把01的含義改變的思想妙啊。

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

using namespace std;
typedef long long ll;

int n,m,fake;
ll f[12][4200000];
bool qwq[4200000];

int main()
{
    while(scanf("%d%d",&n,&m)!=EOF&&n!=0)
    {
        fake=(1<<m)-1;
//      for(int i=0;i<=fake;i++)
//          if(check(i)) qwq[i]=1;
        for(int i=0;i<=fake;i++)
        {
            bool cnt=0,has_odd=0;
            for(int j=0;j<m;j++)
                if((i>>j)&1) has_odd|=cnt,cnt=0;
                else cnt^=1;
            qwq[i]=has_odd | cnt ? 0 : 1;
        }
        f[0][0]=1;
        for(int i=1;i<=n;i++)
            for(int j=0;j<=fake;j++)
            {
                f[i][j]=0;
                for(int k=0;k<=fake;k++)
                {
                    if(j&k) continue;
                    if(!qwq[j|k]) continue;
                    f[i][j]+=f[i-1][k];
                }
            }
        printf("%lld\n",f[n][0]);
    }
    return 0;
}