铺砖头问题(完美)——爆搜&&插头DP
题意
给定一个 $n \times m$ 的格子,每个格子被染成了黑色或白色。现在要用 $1 \times 2$ 的砖块覆盖这些格子,要求块与块之间互不重叠,且覆盖了所有白色的格子,但不覆盖任意黑色格子。求一共有多少种覆盖方法。结果对 $M$ 取余。($1 \leq n\leq 15, 1 \leq m\leq 15$)
分析
爆搜,从最左上方开始放置,从左至右,从上到下,每找到一种方案返回1.
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int mod = 1000000007; int n, m; const int maxn = 15+5; bool color[maxn][maxn]; //false为白,true为黑 bool used[maxn][maxn]; int rec(int i, int j) { if(j == m) return rec(i+1, 0); //进入下一行 if(i == n) return 1; //已经覆盖所有空格 if(used[i][j] || color[i][j]) return rec(i, j+1); //不需要在(i, j) 上放置砖块 //尝试2种放法 int res = 0; used[i][j] = true; //横着放 if(j+1 < m && !used[i][j+1] &&!color[i][j+1]) { used[i][j+1] = true; res += rec(i, j); used[i][j+1] = false; } //竖着放 if(i+1 < n && !used[i+1][j] && !color[i+1][j]) { used[i+1][j] = true; res += rec(i, j+1); used[i+1][j] = false; } used[i][j] = false; return res % mod; } int main() { //init color scanf("%d%d", &n, &m); printf("%d,", rec(0, 0)); } }
这个方法的时间复杂度为 $O($nm\cdot 2^{nm})$,会超时。也无法使用记忆化搜索。
但是仔细思考会发现,实际参数并没有这个多种可能。
不确定的只有每一列里还没有查询的格子种的最上面的一个,共 $m$ 个。从而可以把这 $m$ 个格子通过状态压缩编码进行记忆化搜索,复杂度为 $O(nm\cdot 2^{m})$.
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int mod = 1000000007; int n, m; const int maxn = 15+5; const int M = 1000000007; bool color[maxn][maxn]; //false为白,true为黑 int dp[2][1 << maxn]; void solve() { int* crt = dp[0], *nxt = dp[1]; crt[0] = 1; for(int i = n-1;i >= 0;i--) for(int j = m-1;j >= 0;j--) { for(int used = 0; used < (1 << m); used++) { if((used >> j & 1) || color[i][j]) nxt[used] = crt[used & ~(1 << j)]; //不需要在(i, j)放置砖块 else { //尝试2种放法 int res = 0; if(j+1 < m && !((used >> (j+1)) & 1) && !color[i][j+1]) //横着放 res += crt[used | (1 << (j+1))]; if(i+1 < n && !color[i+1][j]) res += crt[used | (1 << j)]; //竖着放 nxt[used] = res % M; } } swap(crt, nxt); } printf("%d\n", crt[0]); } int main() { //init color scanf("%d%d", &n, &m); solve(); }
From:《挑战程序设计竞赛》
个性签名:时间会解决一切