poj 2411 Mondriaan's Dream


狀態壓縮DP

經典覆蓋問題,輸入n和m表示一個n*m的矩形,用1*2的方塊進行覆蓋,不能重疊,不能越出矩形邊界,問完全覆蓋完整個矩形有多少種不同的方案

其中n和m均為奇數的話,矩形面積就是奇數,可知是不可能完全覆蓋的。接着我們來看n*m為偶數的情況

DP前先處理一下,交換n和m使n較大m較小,這樣能減少狀態數

另外數據中是有重復的,所以開辟一個ans數組來記錄每組數據的結果,如果遇到相同的數據則不要計算直接輸出答案

不用這個ans數組的話也不會超時,這個代碼是跑出了950ms,加了這個記錄答案的數組時間變為600ms

接着就看注釋部分的講解即可

/*
最上面的為第1行,最下面為第n行
從上到下按行DP
其中一行的狀態我們用一個二進制表示,0表示沒有被覆蓋,1表示被覆蓋了
最后得到一個01串,這個串變回十進制就是一個狀態
定義狀態dp[i][s],表示前i-1行已經放滿,第i行的狀態為s的方案數
狀態轉移方程為 dp[i][s]=sum{ dp[i-1][ss] } ,其中狀態s與狀態ss兼容
這個狀態轉移方程的內涵在於理解s和ss何為兼容
首先我們約定一個放置方法,就是豎着放的時候,我們暫且將其稱為“上凸型擺放”
因為豎放必然占據第i-1行和第i行,我們約定這個方塊是屬於第i行的,也就是說它凸上去了
那么要在第i行的第j列豎放一個方塊的話,第i-1行第j列必須沒有方塊
也就是說,第i行的放置是受到第i-1行的限制的,反過來說在第i行豎放了方塊,也會影響第i-1行的狀態
所以這樣就可以講解一下狀態轉移方程了,前i-2行已經放滿了,第i-1行的狀態為ss(dp[i-1][ss])
此時在第i行開始放一些方塊,放的方法不定,可能橫放可能豎放,但是按這個方案放完后
第i-1行剛好被填滿,且第i行的狀態變為了s,所以不難想到第i-1行的狀態ss到第i行的狀態s這個轉移是唯一的
所以有 dp[i][s]=sum{ dp[i-1][ss] }
最后我們詳細討論一下s和ss在什么情況下是兼容的
1.第i行的第j列為1,第i-1行的第j列為1,這樣的話,說明第i行的第j列一定不是豎放而是橫放否則會與第i-1行的第j列沖突
  所以馬上緊接着判斷第i行第j+1列,如果是1,那么滿足橫放的規則,同時也要第i-1行第j+1列也要為1,否則的話這個格子沒辦法填充,
  成立后向左移動兩格
  不滿足上述條件的,就是兩個不兼容或者不合法的狀態
2.第i行第j列為1,第i-1行第j列為0,那么說明第i行第j列應該豎放並填充第i-1行第j列,成立后向左移動一格
3.第i行第j列為0,說明不放方塊,那么第i-1行第j列必須為1,否則沒法填充這個格子。若第i-1行第j列也為0,不兼容不合法
  (至於第i行第j列這個格子空着干什么,其實就是留出來給第i+1行豎放的時候插進來的)

那么目標狀態是什么,就是dp[n][maxs],maxs表示全部是1的串,即第n-1行以上全部覆蓋滿,第n行的狀態為maxs,即沒有空着的格子,也全部覆蓋滿了
即整個矩形全部被覆蓋滿了的狀態

最后是第1行的初始化問題,因為約定了“上凸型擺放”,所以第1行是不能豎放方格的,只能橫放方格,
每橫放一個必定占據兩個格子,所以在判斷一個狀態(那個01串)的時候,連着的1的個數必定為偶數,如果出現了單獨的1,說明不合法
*/

#include <cstdio>
#include <cstring>
#define N 15
#define MAX (1<<11)+10

long long dp[N][MAX];
long long ans[N][N];
int n,m;

bool init(int s)
{
    for(int k=0; k<m; )
    {
        if(s & (1<<k))
        {
            if(k==m-1) return false;
            if(s&(1<<(k+1))) k+=2;
            else return false;
        }
        else k++;
    }
    return true;
}

bool ok(int s, int ss)
{
    for(int j=0; j<m; )
        if(s & (1<<j)) //第i行第j列為1
        {
            if( ss & (1<<j)) //第i-1行第j列也為1,那么第i行必然是橫放
            {
                //第i行和第i-1行的第j+1都必須是1,否則是非法的
                if( j==m-1 || !(s&1<<(j+1)) || !(ss&(1<<(j+1))) ) return false;
                else  j+=2;
            }
            else j++; //第i-1行第j列為0,說明第i行第j列是豎放
        }
        else //第i行第j列為0,那么第i-1行的第j列應該是已經填充了的
        {
            if(ss&(1<<j)) j++;//已經填充
            else return false;
        }
    
    return true;
}

void solve()
{
    int maxs;
    if(n<m)
    { n=n^m; m=n^m; n=n^m; }
    //交換后n是行m是列,m較小,那么狀態數也可以相應減少
    maxs=(1<<m)-1;
    memset(dp,0,sizeof(dp));
    
    for(int s=0; s<=maxs; s++) //枚舉第一行所有可能的狀態
        if(init(s))
        {
            dp[1][s]=1; //方案數都是1
            //printf("%d\n",s);
        }

    for(int c=2; c<=n; c++) //按行dp
        for(int s=0; s<=maxs; s++) //第i行的狀態
            for(int ss=0; ss<=maxs; ss++) //第i-1行的狀態
                if(ok(s,ss))
                    dp[c][s] += dp[c-1][ss];
    
    
    printf("%lld\n",ans[n][m]=ans[m][n]=dp[n][maxs]);
}

int main()
{
    memset(ans,-1,sizeof(ans));
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        if(!n && !m) break;
        if(!ans[n][m]) 
        {
            printf("%lld\n",ans[n][m]);
            continue;
        }
        if(n&1 && m&1) 
        {
            ans[n][m]=ans[m][n]=0;
            printf("0\n");
            continue;
        }
        solve();
    }
    return 0;
}

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM