当前位置: 首页 > ds >正文

动态规划3、悟到核心

题目引入:

#include <iostream>
#include <queue>
#include <vector>
#include <tuple>
#include <algorithm>
#include <cstring>
using namespace std;// dp[i]考虑前i家店能收获的最大价值
// 面对第i家店铺,你可以选择偷,或者不偷
// 偷的话,i-1家就不能偷int main() {int N;cin >> N;vector<int> V(N + 1);for (int i = 1; i <= N; i++) {cin >> V[i];}if (N == 0) {cout << 0 << endl;return 0;}if (N == 1) {cout << V[1] << endl;return 0;}vector<int> dp(N + 1);dp[1] = V[1];dp[2] = max(V[1], V[2]);for (int i = 3; i <= N; i++) {dp[i] = max(dp[i - 1], dp[i - 2] + V[i]);}cout << dp[N] << endl;return 0;
}

dp的核心:

用子问题的最优解来构建原问题的最优解

1. 最优子结构

这是dp的”根基“

——大问题的最优解,一定可以由子问题的最优解提出

比如:前i家店的最大价值,只跟前i-1和i-2家的最大价值有关

2. 状态设计

这是dp的“语言”

——我该怎么用“状态”表示我解决问题的中间结果?

dp[i]表示“处理到第i家店时的最大价值‘

dp[x][y][fuel]表示走到xy位置,持有fuel汽油时的最小花费

3. 状态转移方程

这是dp的”灵魂表达式“

——状态A是怎么从状态B、C、D...推出来的?

4. 记忆化

记忆化搜索、自底向上的dp表格...

——已经知道结果了,就别再重新算一遍!

#include <iostream>
#include <queue>
#include <vector>
#include <tuple>
#include <algorithm>
#include <cstring>
using namespace std;// 甚,原来是区间dp啊!
int dp[11][11];
bool vis[11][11];string A, B;
int n;int dfs(int l, int r) {if (l > r) return 0;if (vis[l][r]) return dp[1][r]; // 剪枝处理vis[l][r] = true;int i = n - (r - l + 1);int take_left = (int)A[l] * (int)B[i] + dfs(l + 1, r);int take_right = (int)A[r] * (int)B[i] + dfs(l, r - 1);return dp[l][r] = max(take_left, take_right);
}int main() {while (cin >> A >> B) {n = A.size();memset(dp, 0, sizeof(dp));memset(vis, 0, sizeof(vis));cout << dfs(0, n - 1) << endl;}return 0;
}

直接暴力dfs,字符数量少

但是如果字符数量大的话我们就要建dp表格了!

#include <iostream>
#include <queue>
#include <vector>
#include <tuple>
#include <algorithm>
#include <cstring>
using namespace std;// 本题的状态为:dp[l][r] = 从 A[l..r] 与 B 匹配能得到的最大能量int main() {string A, B;while (cin >> A >> B) {int n = A.size();vector<vector<int>> dp(n, vector<int>(n, 0));// 区间长度for (int len = 1; len <= n; len++) {for (int l = 0; l + len <= n; l++) {int r = l + len - 1;int i = n - (r - l + 1);if (l == r) {dp[l][r] = A[l] * B[i];}else {dp[l][r] = max(A[l] * B[i] + dp[l + 1][r],A[r] * B[i] + dp[l][r - 1]);}}}cout << dp[0][n - 1] << endl;}return 0;
}

几乎所有 DP 题目,一开始都可以用 DFS 去建模,而动态规划只是对 DFS 的一种优化,优化掉了“重复子问题”的计算。

🍃第一步:DFS 是怎么来的?

很多问题,我们一开始会有一种“我从起点一路走到底”的直觉,这就很像 DFS —— 沿着一个路径一直走下去。

举个例子:

比如之前那个盗贼问题(不能偷相邻的店):

我们可以这样思考:

我现在站在第 i 家店门口,我可以选择“偷”或者“不偷”。

那我们就写一个函数:

int dfs(int i) { if (i <= 0) return 0; return max(dfs(i - 1), dfs(i - 2) + V[i]); // 不偷 or 偷 }

你看,这就是标准的 DFS:尝试所有选择


🌱第二步:DFS 有什么缺点?

它的最大问题就是:

会重复算很多次!!!

比如你在求 dfs(10) 的时候,它会递归去算 dfs(9) 和 dfs(8),但 dfs(9) 又会去算 dfs(8) 和 dfs(7),你看:

dfs(8) 被重复算了两次
dfs(7) 被重复算了三次
dfs(6) 被重复算了五次……🌪️

这样效率就很差!


🌟第三步:怎么办?优化它!

于是我们就说:

我干嘛重复算呀!我算过的结果,记下来不就行了吗!

这就有了“记忆化搜索”:

int memo[10001]; // 初始化为 -1 int dfs(int i) { if (i <= 0) return 0; if (memo[i] != -1) return memo[i]; return memo[i] = max(dfs(i - 1), dfs(i - 2) + V[i]); }

这样我们就把DFS + 记忆化结合起来了。

而这个做法,其实就是“动态规划的雏形”。


💡第四步:为什么说 DP 是 DFS 的优化?

我们刚刚不是说了吗?DFS 会重复计算 → 我们记下来 → 效率提高。

那我们更进一步,干脆不要递归了,我直接从底往上推,像搭积木一样一步步算出来。

就变成了“表格式DP”:

dp[0] = 0; dp[1] = V[1]; dp[2] = max(V[1], V[2]); for (int i = 3; i <= N; i++) { dp[i] = max(dp[i - 1], dp[i - 2] + V[i]); }

是不是完全和刚才 DFS 的逻辑一样?

只是——不再递归、不再走回头路、效率变得超高✨

http://www.xdnf.cn/news/7985.html

相关文章:

  • 【DB2】SQL1639N 处理
  • 建立java项目
  • 免费iOS签名的能使用吗?
  • 【钱包协议】:WalletConnect 详解
  • 一步步解析 HTTPS
  • 网络安全管理之钓鱼演练应急预案
  • PCB设计教程【入门篇】——电路分析基础-元件数据手册
  • Nginx核心服务
  • 【机器学习基础】机器学习与深度学习概述 算法入门指南
  • R语言速查表
  • 什么是瞬态动力学?
  • 从芯片互连到机器人革命:英伟达双线出击,NVLink开放生态+GR00T模型定义AI计算新时代
  • ILRuntime中实现OSA
  • CAU人工智能class3 优化器
  • Python MD5加密算法脚本
  • Java线程池调优与实践经验
  • JavaScript-DOM-02
  • DS18B20 温度传感器实验探索与实践分享​
  • 深度学习Y8周:yolov8.yaml文件解读
  • Leetcode-3 判断根结点是否等于子结点之和
  • Universal Media Server (UMS)部署指南
  • HTTP相关内容
  • 【Java高阶面经:数据库篇】12. MySQL锁机制全解:从行锁到死锁优化的深度指南
  • 十七、面向对象底层逻辑-MessageSource接口设计
  • 鸿蒙开发:应用上架第二篇,申请发布证书
  • CSS 链接样式全解析:从基础状态到高级交互效果
  • Docker的网络介绍
  • canvas(二)-动画(2d)
  • 人工智能解析:技术革命下的认知重构
  • 贪心算法 Part04