描述
歷經千辛萬苦,小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;
}