这个题目的题意很容易理解,在一个N*M的格子里,我们现在有两种类型的 砖块,1 * 2 和 2 * 1,问一共有多少种方案,可以将整个N*M的空间都填满。
最简单的例子就是下面的了:
题解:
状态标记 横放和竖放的下一个均为1,竖放的上一个和不放置为0 ,每行可以转化为1个2进制数。为什么要这样呢,应为这样表示肯定是包括了pre(前一行)和now(后一行)的所有状态的(而且多了很多,后面的主要工作是怎样删掉这些多的),而且可以区分,还容易区分。也就是说从pre转到now状态时要做的事不是很多。
或者还有一种理解方法:pre为1表示now与pre无关,为0就表示有关;也就是:
1. 在横着贴砖的时候,(i, j), (i, j+1) 都是1,这个值其实对下一行如何选择没有影响。
2. 竖着贴砖的第二个,我们也选择了1, 因为这个砖头结束了,对下一行如何选择依然没有影响。
3. 而竖着的第一个砖头,这个砖头是对下面有影响的,如果(i,j)是0,那么(i+1, j)只有是1的情况下才能满足条件。
那好下面介绍3种方法(越来越叼):
一:暴力:就是三重循环,如果pre的状态k与now的状态j匹配的话dp[i][j] += dp[i-1I][k];
然后懒得写了,具体想知道就看代码和注释吧:
//这个代码是直接复制别人的;
#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];/*这个滚动有点瓜皮^.^;
}
/*
那他上面那两个判断就很有灵性了;可能很多人还没理解;我在举例子来说明一下。
首先我们那样定义就是有两个地方要解决,第一个是你不能在第i-1行放一个竖的(放0)又在第i行放一个竖的;
也就是双零情况,所以只要满足i|j是满的(每位都为一)就行了;
第二种就是不能放半个横的;又懒得讲了,自己举个例子就好了;
*/
}
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;
}