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;
}