记忆化搜索
💡 核心思想
记忆化搜索是在递归搜索的基础上,将已计算过的子问题结果缓存起来,避免重复计算。本质上是动态规划的自顶向下实现方式。通常能把指数级复杂度优化为多项式级。
记忆化搜索是理解动态规划的最好跳板。很多同学觉得 DP 难,是因为自底向上的递推不好想。但如果你能用递归把问题想清楚,加上一个备忘录数组,就是记忆化搜索——和 DP 本质相同,只是写法不同。在 CSP 竞赛中,记忆化搜索和递推 DP 都可以,选你更顺手的。
🎯 直觉理解
想象你在解一棵很大的递归树。没有记忆化的话,同样的子问题会被算很多很多次。记忆化就像你在草稿纸上记录:"$f(5)=120$,算过了别再算了"。下次遇到 $f(5)$ 直接查表。
📝 算法流程
- 写出朴素的递归搜索
- 添加备忘录(数组或map)存储已计算结果
- 递归入口先查表:算过直接返回
- 没算过则递归计算,存入备忘录后返回
$$\text{朴素斐波那契: } O(2^n) \to \text{记忆化: } O(n)$$
📊 复杂度分析
| 指标 | 复杂度 |
|---|---|
| 时间 | 等于子问题数量 × 每个子问题耗时 |
| 空间 | 等于子问题数量 |
💻 参考实现(C++)
C++ (C++17)
#include <bits/stdc++.h>
using namespace std;
long long memo[1005];
long long fib(int n) {
if (n <= 1) return n;
if (memo[n] != -1) return memo[n]; // 查表
return memo[n] = fib(n-1) + fib(n-2); // 计算+存储
}
int main() {
memset(memo, -1, sizeof(memo));
int n; cin >> n;
cout << fib(n) << endl;
return 0;
}⚠️ 常见坑点
备忘录初始化值要和合法答案区分开(如用 -1 表示未计算)
状态设计不合理导致子问题数量爆炸
递归深度过大——可能需要改写为迭代 DP
用 map 做备忘录时常数较大
📚 相关题目
| 题目 | 来源 | 难度 | 备注 |
|---|---|---|---|
| P1048 采药 | 洛谷 | CSP-J | 记忆化/01背包 |
| P1434 滑雪 | 洛谷 | CSP-S | 记忆化搜索经典题 |