动态规划入门:从记忆化搜索说起

星期六, 五月 23, 2026 | 1分钟阅读 | 更新于 星期六, 五月 23, 2026

@
动态规划入门:从记忆化搜索说起

想象你在玩一个贪吃蛇游戏,每次吃到食物后身体变长。现在要计算吃到第n个食物时的最大得分——如果每次都暴力枚举所有可能路径,时间复杂度会爆炸!这时候动态规划(Dynamic Programming, DP)就像给贪吃蛇装了GPS,用「记住历史」的方式避免重复计算,让你从超时秒切到轻松AC。


核心概念讲解

动态规划的核心思想是「最优子结构」和「无后效性」

  • 最优子结构:问题的最优解包含子问题的最优解(比如爬楼梯问题,爬到第n阶的最优解由n-1和n-2阶决定)。

  • 无后效性:当前状态只与之前状态有关,与未来无关(比如背包问题,选了哪些物品不影响后续决策)。

记忆化搜索(Memoization)是DP的「暴力+备忘录」版本:

  1. 递归尝试所有可能路径(暴力)。

  2. 遇到重复子问题时直接查表返回结果(备忘录)。

文字示意图说明

假设计算fib(5)时,先递归调用fib(4)fib(3);当再次需要fib(4)时,直接从缓存中读取结果而非重新计算。


C++代码示例(斐波那契数列)


#include <bits/stdc++.h>

using namespace std;

// dp数组,dp[i]表示第i项斐波那契数

vector<long long> dp(100, -1); // 使用long long防止溢出,初始化为-1表示未计算

long long fib(int n) {

if (n <= 1) return n; // 边界条件

if (dp[n] != -1) return dp[n]; // 已计算则直接返回

// 递归计算并缓存结果

dp[n] = fib(n-1) + fib(n-2);

return dp[n];

}

int main() {

cout << fib(50) << endl; // 输出12586269025(正确值)

return 0;

}

注释说明

  • long long类型确保存储大整数(如第50项斐波那契数)。

  • -1作为未计算的标记,需与合法值区分。

  • 每个子问题仅计算一次,时间复杂度O(n)。


算法分析

  • 时间复杂度:O(n)(每个子问题只计算一次)。

  • 空间复杂度:O(n)(需存储dp数组)。

  • 适用场景:有重叠子问题和最优子结构的问题(如背包、最长公共子序列等)。

  • 局限性:若问题无重叠子问题(如简单排序),记忆化搜索效率低于普通递归。

注意事项

  1. 数据范围:注意数值溢出(如本题必须用long long)。

  2. 初始化:明确所有边界条件的初始值(如dp[0]=0, dp[1]=1)。

  3. 递归深度:对于大规模输入,可能触发栈溢出,可改用迭代法。


经典例题解析

洛谷 P1027 [NOIP2001 普及组] 数的划分

  • 题意:将正整数n划分为若干正整数之和,求划分方案数(顺序不同视为相同)。

  • 状态定义dp[i][j]表示将整数j划分为最大部分不超过i的划分数。

  • 递推式

$$dp[i][j] = dp[i-1][j] + dp[i][j-i]$$

  • 第一项:不选i,最大部分不超过i-1。

  • 第二项:选至少一个i,剩余部分仍不超过i。

  • 边界条件

  • dp[i][0] = 1(空划分算一种方案)。

  • j < i时,dp[i][j] = dp[i-1][j](无法选i)。

完整代码示例


#include <iostream>

using namespace std;

const int N = 100;

int dp[N][N];

int main() {

int n;

cin >> n;

for (int i = 0; i <= n; ++i) dp[i][0] = 1; // 初始化

for (int i = 1; i <= n; ++i)

for (int j = 1; j <= n; ++j)

dp[i][j] = (j >= i ? dp[i][j-i] : 0) + dp[i-1][j];

cout << dp[n][n] << endl;

return 0;

}

推荐练习

  1. 洛谷 P1215 装箱问题

提示:考察01背包变形,注意物品重量与价值的对应关系。

  1. CF 888C Permutation

提示:排列计数DP,需预处理阶乘逆元模运算。

  1. AtCoder ABC179 D - Divisible Subsequence

提示:记忆化搜索+模运算,统计满足条件的子序列数量。


小结

记忆化搜索通过「空间换时间」优雅地解决重叠子问题,是理解DP的重要起点。下期我们将深入「递推式DP」,教你如何用迭代代替递归,进一步优化常数!

© 2026 NOI Quest - 信息学奥赛学习平台

NOI Quest noiquest.cn · 全国青少年信息学奥林匹克竞赛学习平台