记忆化搜索
💡 核心思想
记忆化搜索是在递归搜索的基础上,将已计算过的子问题结果缓存起来,避免重复计算。本质上是动态规划的自顶向下实现方式。通常能把指数级复杂度优化为多项式级。
记忆化搜索是理解动态规划的最好跳板。很多同学觉得 DP 难,是因为自底向上的递推不好想。但如果你能用递归把问题想清楚,加上一个备忘录数组,就是记忆化搜索——和 DP 本质相同,只是写法不同。在 CSP 竞赛中,记忆化搜索和递推 DP 都可以,选你更顺手的。
🎯 直觉理解
记忆化搜索就是给递归"记笔记":同一个子问题算过一遍,就把结果存下来,下次直接查表,不再重复算。
以斐波那契为例:朴素递归算 $f(40)$ 要重复算 $f(30)$ 几千次;加一个数组缓存,每个值只算一次,复杂度从 $O(2^n)$ 降到 $O(n)$。
记忆化搜索 = 递归 + 缓存,本质是"自顶向下"的动态规划。写法上只要:函数加一个记录数组,算之前先查、算完就存。它比递推版 DP 更好写,因为状态转移方向自然。
📝 算法流程
- 写出朴素的递归搜索
- 添加备忘录(数组或map)存储已计算结果
- 递归入口先查表:算过直接返回
- 没算过则递归计算,存入备忘录后返回
$$\text{朴素斐波那契: } O(2^n) \to \text{记忆化: } O(n)$$
📊 复杂度分析
| 指标 | 复杂度 |
|---|---|
| 时间 | 等于子问题数量 × 每个子问题耗时 |
| 空间 | 等于子问题数量 |
💻 参考实现(C++)
C++ (C++17)
#include
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 | 记忆化搜索经典题 |