1. 程式人生 > >poj 2411 骨牌覆蓋問題 狀壓dp

poj 2411 骨牌覆蓋問題 狀壓dp

題意:用1*2 的矩形通過組合拼成大矩形,求拼成指定的大矩形有幾種拼法

分析:

這題的關鍵在於什麼狀態是合法的,可以這樣想,用1表示有骨牌覆蓋,用0表示空著。在覆蓋第i行的時候,那麼如果覆蓋的狀態是合法的,覆蓋完後第i-1行必須沒有空格了(全是1111),所以可以看出,每一行的狀態必須要有偶數個1相連,為什麼?

由於在做第i行dp時必須完全覆蓋第i-1行,只要抓住這個條件不放就行。

1、如果第i行中有0,則第i-1行一定為1;

2、如果第i行中為1的x列第i-1行為0,說明第i行肯定是豎著放的;

3、如果第i行中為1的x列第i-1行的該列也為1,可能性只有一個,第i行是橫放的,所以第i行的x+1列也必須為1,又因為第i行的x+1列為1是因為橫著放的,所以第i-1行的x+1列也必須為1。

【狀態表示】dp[state][i]第i行狀態為state時候的方案數 

【轉移方程】dp[state][i] += dp[state'][i-1] state'為i-1行的,不與i行狀態state衝突的狀態

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
ll f[1<<11][11];
bool ok[1<<11];
int n,m;
inline bool check(int x)
{
    int bit=0;
    while(x){
        if(x&1)bit++;
        else{
            if(bit&1)return false;
            bit=0;
        }
        x>>=1;
    }
    if(bit&1)return false;
    return true;
}
inline bool judge(int a,int b,int S)
{
    if((a|b)!=S-1)return false;
    return ok[a&b];
}
void init()
{
    memset(ok,0,sizeof(ok));
    for(int s=0;s<(1<<11);s++){
        if(check(s))ok[s]=1;
    }
}

int main()
{
    init();
    //freopen("f.txt","r",stdin);
    int S;
    while(~scanf("%d%d",&n,&m)&&(n+m)){
        if(m>n){
            int t=m;m=n;n=t;
        }
        S=1<<m;
        memset(f,0,sizeof(f));
        for(int s=0;s<S;s++){
            if(ok[s])f[s][0]=1;
        }
        for(int i=1;i<n;i++){
            for(int s=0;s<S;s++){
                for(int ss=0;ss<S;ss++){
                    if(judge(s,ss,S)){
                        f[s][i]+=f[ss][i-1];
                    }
                }
            }
        }
        printf("%lld\n",f[S-1][n-1]);
    }
    return 0;
}

還看到用人用dfs寫的,在轉移的時候逐行判斷,好像更快點。