Luogu P1990 覆盖墙壁
题目分析与思路
本题要求我们计算用 \(1 \times 2\) 和 L 型砖块铺满一个 \(2 \times N\) 墙壁的方案数。这是一个典型的组合计数问题,在网格上进行填充,通常可以采用动态规划(Dynamic Programming, DP)来解决。我们通常会设 dp[i] 为铺满 \(2 \times i\) 墙壁的方案数。一个初步的想法是,考虑最右边一列是如何被覆盖的:
- 如果用一个竖直的 \(1 \times 2\) 砖块覆盖第 \(i\) 列,那么问题就转化为求解铺满 \(2 \times (i-1)\) 墙壁的方案数,即
dp[i-1]。 - 如果用两个水平的 \(1 \times 2\) 砖块覆盖第 \(i-1\) 和 \(i\) 列,那么问题就转化为求解铺满 \(2 \times (i-2)\) 墙壁的方案数,即
dp[i-2]。
这样我们会得到一个类似斐波那契数列的递推式:dp[i] = dp[i-1] + dp[i-2]。但这个递推式是不完整的。为什么呢?让我们来验证一下:
dp[1] = 1(1个竖直砖块)dp[2] = 2(2个竖直砖块 或 2个水平砖块)- 根据上述递推式,
dp[3] = dp[2] + dp[1] = 2 + 1 = 3。 - 然而,题目样例明确指出 \(N=3\) 时有 \(5\) 种方案。
这说明我们遗漏了某些情况。遗漏的正是那些由 L 型砖块构成的铺法。L 型砖块的特点是它无法整齐地填满最右侧的若干列,它总是会留下一个“缺口”,导致墙壁的填充边界不是一条直线。因此,我们需要一个更复杂的状态定义来描述这些带有“缺口”的中间状态。
状态定义与递推
为了解决上述问题,我们需要引入辅助的DP状态。我们观察到,在从左到右铺砖的过程中,填充边界可能不是一条直线,会缺少一个角。我们定义两种状态:
- \(f(n)\):完全铺满一个 \(2 \times n\) 矩形墙壁的方案数。这是我们最终要求解的目标。
- \(g(n)\):铺满一个 \(2 \times n\) 的区域,但右上角(或右下角)缺少一个 \(1 \times 1\) 单元的方案数。由于对称性,这两种缺口情况的方案数是相同的。
下图展示了这两种状态:
f(n) g(n) (右上缺口)
+-------+ +-------+
|#######| |###### |
|#######| |#######|
+-------+ +-------+
<-- n --> <-- n -->
现在,我们来构建这两个状态之间的递推关系。
推导 \(f(n)\)
要得到一个完整的 \(2 \times n\) 墙壁,我们考虑最右侧是如何被填满的:
-
情况 A:在一个完整的 \(2 \times (n-1)\) 墙壁(\(f(n-1)\) 种方案)右侧,加上一个竖直的 \(1 \times 2\) 砖块。
f(n-1) + V -> f(n) +---------+ +---+ +-----------+ |#########| | V | |#########V| |#########| | V | |#########V| +---------+ +---+ +-----------+贡献:\(f(n-1)\)
-
情况 B:在一个完整的 \(2 \times (n-2)\) 墙壁(\(f(n-2)\) 种方案)右侧,加上两个水平的 \(1 \times 2\) 砖块。
f(n-2) + HH -> f(n) +-------+ +----+ +-----------+ |#######| | HH | |#######HH| |#######| | HH | |#######HH| +-------+ +----+ +-----------+贡献:\(f(n-2)\)
-
情况 C:在一个 \(2 \times (n-1)\) 但右上角有缺口的墙壁(\(g(n-1)\) 种方案)上,用一个 L 型砖块恰好补上缺口并覆盖第 \(n\) 列。
g(n-1) + L -> f(n) +---------+ +---+ +-----------+ |######## | |LL | |########LL| |#########| | L | |#########L| +---------+ +---+ +-----------+贡献:\(g(n-1)\)
-
情况 D:对称地,在一个 \(2 \times (n-1)\) 但右下角有缺口的墙壁(同样是 \(g(n-1)\) 种方案)上,用一个旋转过的 L 型砖块填补。
g(n-1) + L' -> f(n) +---------+ +---+ +-----------+ |#########| | L | |#########L| |######## | |LL | |########LL| +---------+ +---+ +-----------+贡献:\(g(n-1)\)
综合以上四种情况,我们得到 \(f(n)\) 的递推式:\(f(n) = f(n-1) + f(n-2) + 2g(n-1)\)。
推导 \(g(n)\)
设 \(g(n)\) 是填满了 \((x, y)\) 其中 \(0 \le x \le 1, 0 \le y < n\) 除了 \((0, n - 1)\) 的方案数。
- 来源一:从 \(g(n-1)\) 出发。\(g(n-1)\) 填满了 \(0 \le y < n-1\) 的格子,除了
(0, n-2)。我们要在其右侧添加砖块形成 \(g(n)\)。可以在 \((1, n-2)\) 和 \((1, n-1)\) 处放置一个水平砖块。这样,\((0, n-1)\) 保持为空,\((0, n-2)\) 被填上。这需要 \((0, n-2)\) 之前是空的,这正是 \(g(n-1)\) 的状态,贡献了 \(g(n-1)\)。 - 来源二:从 \(f(n-2)\) 出发。\(f(n-2)\) 是一个完整的 \(2 \times (n-2)\) 矩形。在其右侧加一个 L 型砖块,覆盖 \((1, n-2), (1, n-1), (0, n-1)\)。这样就完美地构成了 \(g(n)\) 的形状。
综合以上两个来源,我们得到 \(g(n)\) 的递推式:\(g(n) = g(n-1) + f(n-2)\)。
递推关系化简
我们现在有了一个递推方程组:
我们可以通过代数变换消去 \(g(n)\),得到一个只关于 \(f(n)\) 的递推式。由 \((1)\) 式可得:
将 \((A)\) 式中的 \(n\) 替换为 \(n-1\),可得:
由 \((2)\) 式可得:
两边同乘以 \(2\),得:
将 \((A)\) 式和 \((B)\) 式代入 \((C)\) 式:
化简上式:
这是一个简洁的、只关于 \(f\) 的三阶线性递推关系。
边界条件确定
为了使用递推式 \(f(n) = 2f(n-1) + f(n-3)\),我们需要确定初始值 \(f(0), f(1), f(2)\)。
-
\(f(0)\):铺满 \(2 \times 0\) 的墙壁。只有一种方法:什么都不铺。所以 \(f(0) = 1\)。
-
\(f(1)\):铺满 \(2 \times 1\) 的墙壁。只有一种方法:放置一个竖直的 \(1 \times 2\) 砖块。所以 \(f(1) = 1\)。
-
\(f(2)\):铺满 \(2 \times 2\) 的墙壁。有两种方法:
- 两个竖直的 \(1 \times 2\) 砖块。
- 两个水平的 \(1 \times 2\) 砖块。
L 型砖块占 3 个单元,无法铺满 \(2 \times 2\) 的墙。所以 \(f(2) = 2\)。
Python 代码实现
"""
设 f(n) 为铺满 2xN 墙壁的方案数。
推导出的递推关系为:f(n) = 2*f(n-1) + f(n-3)
边界条件:
f(0) = 1 (空墙壁只有一种方案)
f(1) = 1 (一个竖直的 1x2 砖块)
f(2) = 2 (两个竖直的 1x2 或 两个水平的 1x2)
"""
import sys
n = int(sys.stdin.readline())
dp = [1] * (n + 1)
dp[2], MOD = 2, 10000
for i in range(3, n + 1):
dp[i] = (2 * dp[i - 1] + dp[i - 3]) % MOD
print(dp[n])

浙公网安备 33010602011771号