Description
一块n*m的矩阵,现在要用1*2的地砖将其铺满,问所有可能的方案有多少种。地砖可以横着铺也可以竖着铺
Input
多组用例,每组用例占一行包括两个整数n,m,以0 0结束输入
Output
对于每组用例,输出总方案数
Sample Input
1 2
1 3
1 4
2 2
2 3
2 4
2 11
4 11
0 0
Sample Output
1
0
1
2
3
5
144
51205
Solution
由于我们放置小矩形的时候可以横着放也可以竖着放,那么就会产生不同的方法,但是必须满足不产生空的未覆盖的空格子。首先我们用二进制1 0表示在某一个问题放置或者不放置,那么有m列的棋盘,每一行有2*m个状态了,我们现在就找出每行之间的规律。
对于一个矩形有3种方法:横放,竖放,不放。由于第i行只跟第i-1行的放置有关系,因为我们必须保证第i-1行使放满的,现在用dp[i][state]表示第i行状态为state的方法,那么dp[i][state]=sum{dp[i-1][pstate]}.
1 横放
如果第i行第pos列我们选择横放,那么第i行的第pos列及pos+1列都是1了,第i-1行第pos列及pos+1列也都必须为1(保证是满的),及状态转移为:
pos=pos+2,state=state<<2|3,pstate=pstate<<2|3.
2竖放
第i行第pos列我们选择竖放,那么第i行第pos列为1,第i-1行pos列必须是0(因为我们是竖着放的,如果前一行不是空的如何能放下呢),状态转移:
pos=pos+1,state=state<<1|1,pstate=pstate<<1.
3不放
第i行第d列不放,那么第i-1行pos列肯定是1(保证是满的),状态转移:
pos=pos+1,state=state<<1,pstate=pstate<<1|1.
这个题目采用记忆化搜索,对已经计算出的状态值方法记录,还有就是初始化的时候将dp[0][2 << m-1]=1,这样第0行使放满的,就不用单独进行初始化了(单独初始化的时候,由于是第一行,不存在竖着放的可能)。我们的目标就是求dp[n][2 << m-1]
Code
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int n,m;
long long dp[12][(1<<12)];
void init(int state,int pos)
{
if(pos==m)//第一行已经放满
{
dp[0][state]=1;
return ;
}
if(pos+1<=m)//至少还有一个格子没放则可以竖着放
init(state<<1,pos+1);
if(pos+2<=m)//至少还有两个格子没放则可以横着放
init(state<<2|3,pos+2);
}
void dfs(int i,int state,int pstate,int pos)//i表示当前行,state表示当前行状态,pstate表示前一行状态,pos表示当前列
{
if(pos==m)//该行已经放满
{
dp[i][state]+=dp[i-1][pstate];
return ;
}
if(pos+1<=m)//至少还有一个格子没放
{
dfs(i,state<<1|1,pstate<<1,pos+1);//竖着放
dfs(i,state<<1,pstate<<1|1,pos+1);//不放
}
if(pos+2<=m)//至少还有两个格子没放
dfs(i,state<<2|3,pstate<<2|3,pos+2);//横着放
}
int main()
{
while(scanf("%d%d",&n,&m),n||m)
{
if(n&m%2)//奇数个格子显然铺不满
{
printf("0\n");
continue;
}
memset(dp,0,sizeof(dp));//初始化
init(0,0);//预处理出第一行所有状态
for(int i=1;i<n;i++)//由上一行递推出每一行的情况
dfs(i,0,0,0);
printf("%lld\n",dp[n-1][(1<<m)-1]);
}
return 0;
}