描述
历经千辛万苦,小Hi和小Ho终于到达了举办美食节的城市!虽然人山人海,但小Hi和小Ho仍然抑制不住兴奋之情,他们放下行李便投入到了美食节的活动当中。美食节的各个摊位上各自有着非常多的有意思的小游戏,其中一个便是这样子的:
小Hi和小Ho领到了一个大小为N*M的长方形盘子,他们可以用这个盒子来装一些大小为2*1的蛋糕。但是根据要求,他们一定要将这个盘子装的满满的,一点缝隙也不能留下来,才能够将这些蛋糕带走。
这么简单的问题自然难不倒小Hi和小Ho,于是他们很快的就拿着蛋糕离开了~
但小Ho却不只满足于此,于是他提出了一个问题——他们有多少种方案来装满这个N*M的盘子呢?
值得注意的是,这个长方形盘子的上下左右是有区别的,如在N=4, M=3的时候,下面的两种方案被视为不同的两种方案哦!
输入
每个测试点(输入文件)有且仅有一组测试数据。
每组测试数据的第一行为两个正整数N、M,表示小Hi和小Ho拿到的盘子的大小。
对于100%的数据,满足2<=N<=1000, 3<=m<=5。
输出
考虑到总的方案数可能非常大,只需要输出方案数除以1000000007的余数。
样例输入
2 4
样例输出
5
解题思路:看到n,m两个的数据范围不太一样,一个很大,一个很小,可以推断出来这是一道状压题。它有两种蛋糕 1*2 or 2*1,
对于第i行第j列(i,j)这个位置,如果我在这里取的话我可以用1表示,如果不取的话 那我就可以用0表示。
而且我在第i行怎么放,只会影响到i+1行,
那么我们就可以知道 1~i-1一定是放好的,i+2~n行一定是没放的。
这样的话 我们就可以构造一个状态转移方程的雏形。
但是横着取还是竖着取该怎么区分尼,还有就是怎么样判断状态是否兼容。
如果是横着放的话,那么在(i,j)(i,j+1)这两个位置 我就都标记为1,
如果是竖着放的话,那么我就在(i,j+1)的位置标记为1, (i,j)的位置标记为0
举个例子
6 0110 在第2个和第3个位置有一个1,那么我在2 3位置一定横放了一个蛋糕。那么这里的0该怎么办尼,留给下一行。
比如这种情况
行 状态
i 6 0110
i+1 9 1001
那么我们可以发现这两行之间的状态是兼容的,我在第i行没有使用的位置被i+1行的状态给使用了。
行 状态
i 6 0110
i+1 17 1111
这样的状态也是兼容的。
说到这,应该思路都很清晰了,那么我可以先预处理出第一行状态合理的情况。之后我只需要往下推,枚举第i行和第i-1行的状态是否兼容就行。
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define ll long long
const int maxn=1010;
const int mod=1e9+7;
int n,m;
ll dp[maxn][1<<5];
bool check(int i,int m){
int j=0;
while(j<m){
if(!(i&(1<<j))) j++;
else if(j==m-1||!(i&(1<<(j+1)))) return false;
else j+=2;
}
return true;
}
bool jud(int sta,int stb,int m){
int j=0;
while(j<m){
if(!(sta&(1<<j))){
if(!(stb&(1<<j))) return false;
j++;
}
else{
if(!(stb&(1<<j))) j++;
else if(j==m-1||!((sta&(1<<(j+1)))&&(stb&(1<<(j+1))))) return false;
else j+=2;
}
}
return true;
}
int main(){
int i,j,k;
scanf("%d%d",&n,&m);
if(n<m) swap(n,m);
int x=1<<m;
for(i=0;i<x;i++){
if(check(i,m)) dp[1][i]=1;
}
for(i=2;i<=n;i++){
for(j=0;j<x;j++){
for(k=0;k<x;k++){
if(jud(j,k,m))
dp[i][j]=(dp[i][j]+dp[i-1][k])%mod;
}
}
}
printf("%lld\n",dp[n][x-1]);
return 0;
}