题意:给一个n*m大的区域,问用1*2的矩形块填充,能够刚好填满的填充方式有多少?
FEELING:真的吹爆神犇!!www,这个方法也太巧妙了叭qaq!!!!
大神思路蒟蒻理解:
dp[i][s]: 第 i 行状态为s的种类数(保证前i - 1行是全部覆盖的)
我们用01表示(i , j)格子有没有被覆盖,那么每一行都会有一个01串表示的状态。
很明显,第 i 行的状态只受第 i - 1 行状态的影响。如果(i - 1, j)为0,也就是没有被覆盖,那么(i, j)必须要有一个竖着放的矩形块放在(i - 1, j)和(i, j)处,以保证前i - 1行都能被全部覆盖。所以如果枚举到的合法的第 i - 1 行的状态是s,那么第 i 行的状态一定是 ~s,我们设为now_s。(这里的合法状态是指:能够保证它前一行之前的区域都已经被全部覆盖的状态)
很明显第 i 行矩形块必须要竖着放的方式的种类数就是第 i - 1 行状态为s时的种类数。
这就完了嘛?显然没有!第 i 行的矩形块目前只放完了竖着的,那剩余空位置不是还可以横着放吗?无论怎么横着放,不放,放一个,或放几个(只要能空间足够),都是合法状态。因为我们竖着放的矩形块已经保证了第 i 行的状态合法。我们需要把这些合法状态的种类数都更新。由此有了深搜!!!
跑了一个dfs维护每一个合法状态的种类数是多少。
最开始先跑第0行的横着放的合法状态,因为第0行是边界,不需要竖着放保证上一行之前的被完全覆盖,所以这一行的合法状态就是横着放的所有状态。然后从第i = 1行开始枚举i - 1行的每一个合法状态推第 i 行的种类数.
最后答案是dp[n - 1][(1 << m) - 1]
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <limits>
#include <set>
#include <queue>
#include <vector>
#include <stack>
#include <map>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxN = 1e4 + 5;
int n, m;
ll dp[15][1 << 15];
ll line_num;//竖着放矩形块有多少种方式
void dfs(int row, int sta, int pos)//每一行的某个合法状态下还能继续横着放矩形块的方式的种类数
{
if(pos >= m)//pos是第row行的第几个数位。跑完所有的数位当然就加上竖着放的种类数
{
dp[row][sta] += line_num;
return;
}
dfs(row, sta, pos + 1);//更新第row行的初始状态【还没有横着放矩形块】种类数
if(pos <= m - 2 && !(sta & (1 << pos)) && !(sta & (1 << pos + 1)))
dfs(row, sta | (1 << pos) | (1 << pos + 1), pos + 2);//更新横着放至少一个矩形块的合法状态的种类数
}
int main()
{
while(~scanf("%d%d", &n, &m) && (n || m))
{
memset(dp, 0, sizeof(dp));
line_num = 1;//第一行的当然初始化为1
dfs(0, 0, 0);
for(int row = 1; row < n; row ++ )
{
for(int s = 0; s < (1 << m); s ++ )
{
if(dp[row - 1][s])//如果状态不合法就没必要继续往下跑
{
int now_s = ~s & ((1 << m) - 1);
line_num = dp[row - 1][s];//第row行竖着放的种类数有line_num种
dfs(row, now_s, 0);
}
}
}
printf("%lld\n", dp[n - 1][(1 << m) - 1]);
}
return 0;
}