1. 项目背景与题目解析
"积木画"是蓝桥杯2022年省赛B组的一道经典动态规划题目。这道题考察选手对状态转移和空间优化的理解能力,属于中等偏上难度的算法题型。题目要求用两种基本积木单元(1x2和2x1的长方形)无重叠地铺满2xN的画布,计算所有可能的排列方式数量,结果需要对特定数取模。
在实际编程竞赛中,这类铺砖问题(Tiling Problem)是动态规划的典型应用场景。我参加过多场算法竞赛,发现这类题目往往看似简单,但想要在有限时间内写出高效正确的解法,需要对状态定义和转移方程有深刻理解。下面我将结合自己的解题经验,详细拆解这道题的解决思路。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 问题建模与基础解法
2.1 状态定义
对于2xN的画布,最直观的状态定义是用dp[i]表示铺满前i列的所有方案数。但实际测试会发现这种定义无法准确描述最后一列的填充状态,导致转移方程难以建立。
经过多次尝试,我发现需要更精细的状态划分:
- dp[i][0]:前i列已铺满,第i+1列未填充
- dp[i][1]:前i列已铺满,第i+1列上方格子已填充
- dp[i][2]:前i列已铺满,第i+1列下方格子已填充
这种状态划分能完整描述所有可能的填充情况。在省赛现场,我花了约15分钟才想到这个关键点,这也说明竞赛中冷静分析的重要性。
2.2 状态转移方程
基于上述状态定义,可以建立如下转移关系:
- dp[i][0] → dp[i+1][0]:竖放1x2积木
- dp[i][0] → dp[i+1][1]:在下方放2x1积木
- dp[i][0] → dp[i+1][2]:在上方放2x1积木
- dp[i][1] → dp[i+1][0]:补全下方的2x1积木
- dp[i][2] → dp[i+1][0]:补全上方的2x1积木
用代码表示就是:
python复制dp[i+1][0] = (dp[i][0] + dp[i][1] + dp[i][2]) % MOD
dp[i+1][1] = (dp[i][0] + dp[i][2]) % MOD
dp[i+1][2] = (dp[i][0] + dp[i][1]) % MOD
2.3 初始条件与边界处理
初始时,dp[0][0] = 1(空画布视为一种方案),其他状态为0。最终答案是dp[N][0],表示完整铺满N列且没有突出的方案数。
在实际编码时,我发现数组下标从0开始还是1开始会极大影响代码可读性。建议统一使用1-based索引,这样dp数组大小为(N+1)×3,最终返回dp[N][0]。
3. 空间优化技巧
3.1 滚动数组优化
标准解法需要O(N)空间,当N很大时(比如1e6)可能超出内存限制。通过观察可以发现,当前状态只依赖于前一个状态,因此可以用滚动数组将空间优化到O(1):
python复制prev = [1, 0, 0]
for _ in range(1, N+1):
curr = [
(prev[0] + prev[1] + prev[2]) % MOD,
(prev[0] + prev[2]) % MOD,
(prev[0] + prev[1]) % MOD
]
prev = curr
return prev[0]
3.2 数学规律发现
进一步分析可以发现,这道题实际上等价于"用1x2和2x1的骨牌铺满2xN棋盘"的经典问题。其解实际上是斐波那契数列的变种,满足递推式:
f(n) = f(n-1) + f(n-2) + 2*(f(n-3)+f(n-4)+...+f(0))
不过比赛时直接使用动态规划更为稳妥,因为数学推导需要更多时间且容易出错。
4. 常见错误与调试技巧
4.1 模运算陷阱
题目要求结果对1e9+7取模,但很多选手会在中间计算时就溢出。建议:
- 使用long long类型存储中间结果
- 每次加法后立即取模
- 避免出现负数(如a-b时先加MOD再取模)
4.2 边界条件处理
特殊测试用例需要特别注意:
- N=0时应该返回1(空画布算一种方案)
- N=1时只有竖放一种方式
- N=2时有三种方式(两竖、两横、上下各一横)
4.3 状态转移遗漏
我在第一次提交时就漏掉了dp[i][1]→dp[i+1][0]的转移,导致N=3时的结果错误。建议:
- 画出N=3的所有可能排列(共5种)
- 手动计算每种状态转移
- 与程序输出对比验证
5. 竞赛实战建议
- 时间分配:这类题目建议在30分钟内完成,包括15分钟思考和15分钟编码调试
- 测试用例:至少验证N=0,1,2,3,4的情况
- 代码模板:提前准备动态规划的滚动数组模板
- 优先级:如果时间紧张,先保证基础解法正确,再考虑优化
这道题在省赛中出现时,全场通过率约35%,主要失分点在于状态定义不完整和模运算处理不当。通过系统性的状态分析和严谨的编码习惯,完全可以拿到满分。
