1. 程式人生 > >瓷磚覆蓋(狀壓DP)

瓷磚覆蓋(狀壓DP)

pac body small 保持 bin bool str lld NPU

題目描述 Description

用1*2的瓷磚去鋪N*M的地面,問有多少種鋪法

輸入描述 Input Description

第一行有兩數n,m。表示地面的大小

輸出描述 Output Description

共一行,為方案總數

樣例輸入 Sample Input

2 2

樣例輸出 Sample Output

2

分析:

用1*2的磚去恰好鋪滿n*m的空間,對於第k行第j列,有3種情況將該點鋪滿
1:由第k-1行第j列磚豎著鋪將第k行第j列鋪滿
2:由第k行第j列被橫鋪磚鋪滿
3:第k行第j列磚豎著鋪將該點鋪滿
所以對於每一列的情況其實有兩種(1,0)表示該點鋪磚還是不鋪


而對於每一列必須到達的狀態只有一種,就是被鋪滿(1)
但是由上述3種情況將鋪滿方式分成兩種:
0和1表示被k-1行j列豎鋪鋪滿和在k-1行被橫鋪鋪滿
對於每一行列舉每一種到達的狀態j,dp[j]表示到達該狀態有多少種情況
分析對於第k-1行狀態j:10000111
需要到達第k行狀態i: 01111011
如果需要到達第k行j列狀態是0,則必須第k-1行該點狀態不能是0,否則一定是連續兩列豎放沖突
所以到達第k-1行該點只能是1,也就是說i|j一定每一位是1,也可以一步步判斷是否滿足第k行j列是0第k-1行j列是1
如果需要到達第k行狀態j列是1,則假如第k-1行該點是0,則該點狀態可以到達,繼續判斷j+1列
假如第k-1行該點是1,則第k行j列的1一定是橫鋪到達的,所以k行第j+1列一定也被鋪滿為1

從而第k-1行j+1列一定不能豎鋪,必須被橫鋪鋪滿,所以也是1.
於是綜合的第k行j列和第k-1行j列的關系(每一行每一列都表示到達的狀態)
1:下面這種情況從第j列繼續去判斷j+1列
1
0
2:下面這種情況從第j列繼續去判斷j+1列
0
1
3:下面這種情況從第j列判斷第j+1列是否全是1,然後繼續判斷第j+2列
1
1

技術分享圖片
*/
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include 
<queue> #include <algorithm> #include <map> #include <cmath> #include <iomanip> #define INF 99999999 typedef long long LL; using namespace std; const int MAX=(1<<11)+10; int n,m; LL temp[MAX],dp[MAX],bin[15]; bool mark[MAX]; bool check(int i){ while(i){ if(i&1){ i>>=1; if(!(i&1))return false;//第j列是1則第j+1列必須是1 i>>=1;//繼續判斷下一列 }else i>>=1;//繼續判斷下一列 } return true; } void Init(){ memset(mark,false,sizeof mark); memset(temp,0,sizeof temp); for(int i=0;i<bin[m];++i){//初始化第一行和可以到達什麽狀態 if(check(i))temp[i]=1,mark[i]=true; } } void DP(){ for(int k=2;k<=n;++k){ for(int i=0;i<bin[m];++i)dp[i]=0; for(int i=0;i<bin[m];++i){ for(int j=0;j<bin[m];++j){ if((i|j) != bin[m]-1)continue;//每一位或之後必須每一位是1(綜合前面3種情況和分析可知) if(!mark[i&j])continue;//由初始化和前面分析三種情況分析可知i&j必須得到和初始化可以到達的狀態一樣才行 dp[i]+=temp[j];//i可以從j到達,則增加j的方案數 } } for(int i=0;i<bin[m];++i)temp[i]=dp[i]; } } int main(){ bin[0]=1; for(int i=1;i<12;++i)bin[i]=2*bin[i-1]; while(~scanf("%d%d",&n,&m),n+m){ if(n<m)swap(n,m);//始終保持m<n,提高效率 Init(); DP(); printf("%lld\n",temp[bin[m]-1]);//輸出最後一行到達時的狀態必須全部是1 } return 0; }
View Code

優化:

技術分享圖片
<code class="language-cpp">/*優化:不去盲目的列舉所有狀態i和j然後判斷狀態j能否到達i,這樣效率很低,因為能到達i的狀態j很少 
因此對於每種狀態i,由i區搜索能到達i的狀態j,大大提高效率 
有298ms->32ms  
*/   
#include <iostream>  
#include <cstdio>  
#include <cstdlib>  
#include <cstring>  
#include <string>  
#include <queue>  
#include <algorithm>  
#include <map>  
#include <cmath>  
#include <iomanip>  
#define INF 99999999  
typedef long long LL;  
using namespace std;  
  
const int MAX=(1<<11)+10;  
int n,m;  
LL temp[MAX],dp[MAX],bin[15];  
  
bool check(int i){  
    while(i){  
        if(i&1){  
            i>>=1;  
            if(!(i&1))return false;//第j列是1則第j+1列必須是1   
            i>>=1;//繼續判斷下一列   
        }else i>>=1;//繼續判斷下一列   
    }  
    return true;  
}  
  
void Init(){  
    memset(temp,0,sizeof temp);  
    for(int i=0;i<bin[m];++i)if(check(i))temp[i]=1;//初始化第一行  
}  
  
void dfs(int k,int i,int j){  
    if(k == m){dp[i]+=temp[j];return;}  
    if(k>m)return;  
    if((i>>k)&1){  
        dfs(k+1,i,j);  
        if((i>>(k+1))&1)dfs(k+2,i,j|(1<<k)|(1<<(k+1)));  
    }  
    else dfs(k+1,i,j|(1<<k));  
}  
  
void DP(){  
    for(int k=2;k<=n;++k){  
        for(int i=0;i<bin[m];++i)dp[i]=0;  
        for(int i=0;i<bin[m];++i)dfs(0,i,0);  
        for(int i=0;i<bin[m];++i)temp[i]=dp[i];  
    }  
}  
  
int main(){  
    bin[0]=1;  
    for(int i=1;i<12;++i)bin[i]=2*bin[i-1];  
    while(~scanf("%d%d",&n,&m),n+m){  
        if(n<m)swap(n,m);//始終保持m<n,提高效率   
        Init();  
        DP();  
        printf("%lld\n",temp[bin[m]-1]);//輸出最後一行到達時的狀態必須全部是1   
    }  
    return 0;  
}</code>  
View Code

瓷磚覆蓋(狀壓DP)