传送门 >Here<
题意:用1*2的砖块铺满n*m的地板有几种方案
思路分析
状压经典题!
我们以$f[i][j]$作为状态,表示第i行之前全部填完并且第i行状态为j(状压)时的方案数。
我们考虑,对于一个格子,一块砖有3种方法。
(一):横着放。对下一行没有任何影响
(二):竖着放,并且当前这一格作为砖块的下层。那么对下一行也没有任何影响
(三):竖着放,并且当前这一格作为砖块的上层。这种情况对下一行很明显是有影响的。
综上,只有情况3是对下一行有影响的。
所以我们需要一种方法来区分前两种情况和第三种情况。
对于第一种情况,我们把横着的两个位置都染上1,第二种情况的所在格子染成1。唯有第三种情况的时候把当前格子染成0.
所以基本思路就有了,枚举行,然后枚举上下两个状态进行状态转移。如果这两种状态能够吻合(即不冲突),就累计方案数。
那么怎么进行判断是否冲突呢?
首先,一上一下不能再同一个位置出现0——因为这就意味着有两个竖放砖块的上层。
另外,如果一上一下都是1,意味着是两个横放砖块——相邻的两个格子也必须是1.
这样做单独一个的复杂度是4千万左右不会爆,但是考虑到题目有多组询问……打表就好了。
Code
这个代码是表的生成器。
/*By QiXingzhi*/
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long ll;
#define int ll
const int N = ;
const int INF = ;
int h,w,lim;
int f[][N];
inline bool Check(int lst, int cur, int m){
for(int i = ; i < m;){
if(!(lst & ( << (i))) && !(cur & ( << (i)))) return ;
else if((lst & ( << (i))) && (cur & ( << (i)))){
if((lst & ( << (i+))) && (cur & ( << (i+)))){ i += ; continue; }
else return ;
}
else{ ++i; continue; }
}
return ;
}
inline int solve(int n, int m){
memset(f, , sizeof(f));
lim = ((<<(m))-);
f[][lim] = ;
for(int i = ; i <= n; ++i)
for(int j = ; j <= lim; ++j)
if(i == ){ if(Check(lim, j, m)) ++f[][j]; }
else{ for(int k = ; k <= lim; ++k) if(Check(k, j, m)) f[i][j] += f[i-][k]; }
return f[n][lim];
}
main(){
while(){
scanf("%d %d", &h, &w);
if(!h && !w) break;
if((h & ) && (w & )){ printf("0\n"); continue; }
printf("%lld\n",solve(h,w));
}
return ;
}