n*m的走道铺满1*2的地砖,求铺设方案数。
1 <= N,M <= 11
状压dp。我们知道这题中上一行的状态可以一定程度上决定下一行,且铺一块砖的方式只有两种:竖放和横放。
不妨用1 1表示横放的砖块,上0下1来表示竖放的砖块。为什么这样表示?
- 横放砖块对下一行完全没有影响
- 竖放砖块的下半部分填充了下一行的一个格子。
- 竖放砖块的上半部分对下一行有影响:如果上一行某一位是0,那么下一行这位只能是1。
- 为了保证最后一行没有竖放的砖块,我们只需要保证最后一行都是1。
用dp[i][j]表示第i行状态为j的方案数,那么dp[n][2^m-1]就是答案。
之后就是bottom-up过程了,值得注意的是有许多非法情况需要判断。
- 例如第i行第k位已经是0,那么i-1行对应位一定是1,否则非法。如果合法继续检测(i,k+1)。
- (i,k)=1,那么继续分类:
- (i-1,k)=0,合法,继续检测(i,k+1)。
- (i-1,k)=1,则只可能是(i,k+1)=(i-1,k+1)=1,否则非法。如果合法继续检测(i,k+2)。
- 对于第一行:
- (0,k)=0,继续检测(0,k+1)。
- (0,k)=1,则(0,k+1)=1,继续检测(0,k+2)。
- 任意需要检测(0,k+2)且k==m-1的情况,都是非法的。
嗯,就这么多。
#include <bits/stdc++.h>
using namespace std;
const int maxrow = 11;
const int maxstat = 1<<11;
int h, w;
long long dp[maxrow][maxstat];
inline bool first_ok(int stat)
{
for (int i = 0; i < w; )
if (stat & (1<<i))
{
if (i == w-1 || !(stat & (1<<(i+1))) )
return 0;
i += 2;
}else ++i;
return 1;
}
inline bool judge(int a, int b)
{
for (int i = 0; i < w; )
{
if (!(a & (1<<i)))
{
if (!(b & (1<<i))) return 0;
++i;
}else
{
if (!(b & (1<<i))) ++i;
else if (i == w-1 || !(( a & (1<<(i+1)) ) && ( b & (1<<(i+1)) )))
return 0;
else i += 2;
}
}
return 1;
}
int main()
{
while (cin >> h >> w)
{
if (!h && !w) break;
if (w > h) swap(w, h);
int all = 2 << (w-1);
memset(dp, 0, sizeof dp);
for (int i = 0; i < all; ++i)
if (first_ok(i)) dp[0][i] = 1;
for (int i = 1; i < h; ++i)
for (int j = 0; j < all; ++j)
for (int k = 0; k < all; ++k)
if (judge(j, k)) dp[i][j] += dp[i-1][k];
printf("%lld\n", dp[h-1][all-1]);
}
return 0;
}