POJ 2411: Mondriaan's Dream
题目链接:http://poj.org/problem?id=2411
题目大意:在$h imes w$($1 leqslant h,w leqslant 11$)的网格中放满$1 imes 2$的木条,问有几种放置方案。
状压dp
定义横向放置的木条为$11$(横向),纵向放置的木条为$_1^0$(纵向).
这样检查转移的合法性只需要检查前一行状态$s_{i-1}$与当前行状态$s_i$的位或($s_{i-1}|s_i$)是否全为$1$,且横向的$1$是否均匹配.
定义状态$dp[i][s]$表示第$i$行状态为$s$的方案数,若状态$s_{i-1}$由状态$s_i$转移合法,则有$dp[i][s_{i-1}]+=dp[i-1][s_i]$.
最后一行不能放置向下的纵向木条,即最后一行全为$1$,故$ans=dp[n-1][(1<<m)-1]$.
可以预处理合法的状态并标记以降低复杂度。
总时间复杂度为$O(2^{MAX{m}}+n2^{2m})$.
代码如下:
1 #include <cstdio> 2 #include <cstring> 3 using namespace std; 4 typedef long long ll; 5 int n,m; 6 ll dp[15][1<<11]; 7 bool s[1<<11]; 8 bool valid(int s){ 9 int tot=0; 10 while(s){ 11 if(s&1)tot++; 12 else if(tot&1)return 0; 13 s>>=1; 14 } 15 return tot%2==0; 16 } 17 void init(){ 18 for(int i=0;i<(1<<11);++i) 19 s[i]=valid(i); 20 } 21 bool check(int x,int y){ 22 if((x|y)+1!=(1<<m))return 0; 23 return s[x&y]; 24 } 25 int main(void){ 26 init(); 27 while(~scanf("%d%d",&n,&m)){ 28 if(n==0&&m==0)break; 29 memset(dp,0,sizeof(dp)); 30 for(int i=0;i<(1<<m);++i) 31 if(s[i])dp[0][i]=1; 32 for(int i=1;i<n;++i){ 33 for(int j=0;j<(1<<m);++j)if(dp[i-1][j]){ 34 for(int k=0;k<(1<<m);++k)if(check(j,k)){ 35 dp[i][k]+=dp[i-1][j]; 36 } 37 } 38 } 39 printf("%lld ",dp[n-1][(1<<m)-1]); 40 } 41 }