POJ2411-Mondriaan's Dream(輪廓線DP)

題目鏈接

題意

用2*1的骨牌填充滿m*n的矩陣,問有多少種不同的填法。

思路

有、沒有用1、0表示,狀壓DP的思想,但是區別是用的是2*1的骨牌,填一行後可能會有不填的情況,所以輪廓線DP(或插頭DP)就是解決這種每次一行不是整個的問題。記錄的狀態,不是一整行的狀態,而可能是一個斜着的狀態,狀態的第一個位置並不一定對應着每行的第一個位置。

輪廓線DP詳解:https://blog.csdn.net/u013480600/article/details/19499899

圖示:https://blog.csdn.net/lvmaooi/article/details/79702273

每次對(i,j)位置只有三種情況,不放,橫放,豎放。其中橫放是(i-1,j)和(i,j),豎放是(i,j-1)和(i,j),即不能佔右邊和下邊的位置。

對於狀態K5K4K3K2K1和O,O上面的K5如果是0,只能夠豎放。

每次的狀態轉移,是對每一個小格子(i,j),不同的狀態K5K4K3K2K1相應的有多少種情況。所以輪廓線DP的狀態轉移不是對每一行,而是把行拆開成輪廓,對於每一個格子,相應的輪廓狀態的情況。

dp[2][1<<N],1<<N是狀態個數,2是滾動數組,因爲只需要記錄前一個格子對應的狀態,然後推出下一個格子的狀態,其他的都是中間需要的,相當於滾動數組了。

2*1骨牌填矩陣的問題,也可以用狀態轉移矩陣解決+快速冪解決,特別是對於m,n有一個較小,有一個很大(1e9)。

輪廓線DP時間複雜度O(mn  *  2^m) ,如果mn有一個特別大就不適用了。


#include<cstdio>
#include<algorithm>
#include<cstring>
#define ll long long
using namespace std;
const int N = 11;
ll dp[2][1<<N];
int m,n,cur;

void update(int a,int b)
{
    if(b&(1<<m)) dp[cur][b^(1<<m)] += dp[cur^1][a];//如果第一個是1,才更新
}

int main()
{
    while(~scanf("%d%d",&m,&n))
    {
        if(m==0&&n==0) break;
        if(n<m) swap(n,m);
        cur = 0;
        memset(dp[cur],0,sizeof(dp[cur]));
        dp[cur][(1<<m)-1] = 1;
        for(int i=0;i<n;i++){
            for(int j=0;j<m;j++){
                cur ^= 1;
                memset(dp[cur],0,sizeof(dp[cur]));
                for(int k=0;k<(1<<m);k++){
                    update(k,k<<1);//不放
                    if(i && ((k&(1<<(m-1)))==0) ) update(k,(k<<1)^(1<<m)^1);//豎放
                    if(j && ((k&1)==0) ) update(k,(k<<1)^3);//橫放
                }
            }
        }
        printf("%lld\n",dp[cur][(1<<m)-1]);
    }
    return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章