DP 详解

news/2025/3/12 13:20:20/文章来源:https://www.cnblogs.com/George222/p/18508452

DP 概述

DP(Dynamic programming,全称动态规划),是一种基于分治,将原问题分解为简单子问题求解复杂问题的方法。

动态规划的耗时往往远少于朴素(爆搜)解法。

动态规划 and 递归

之前说过,动态规划也是分治思路,而递归更是传统的分治思路,但时间复杂度却大相径庭,为什么呢?

动态规划是 自顶向上 思想,而递归是 自顶向下 解法。

自顶向上 and 自顶向下?

自顶向上

意思很简单,从下往上推导:\(f(1) \rightarrow f(2) \rightarrow \dots \rightarrow f(n - 1) \rightarrow f(n)\)

这也是为什么 动态规划算法 脱离了 递归 的函数,改用循环迭代推到的原因。

自顶向下

反过来,自顶向下就是从上往下推,触底后在将结果返回回来。

\(f(n) \rightarrow f(n - 1) \rightarrow \dots \rightarrow f(2) \rightarrow f(1) \rightarrow f(2) \rightarrow f(3) \rightarrow \dots \rightarrow f(n - 1) \rightarrow f(n)\)

这也是为什么递归比动态规划时间复杂度高的多的原因。

我们可以看出,动态规划更像是递推算法的 plus 版。

状态转移方程

状态转移方程,就是如何将子问题转移至父亲问题的公式。

在简单 DP 中,转移方程可以直接套用至 dfs, bfs 等爆搜算法。

DP 最难的部分就是列出状态转移方程,如果没有状态转移方程,一切都白搭。

例:设 \(f_i\) 为数列第 \(i\) 为的数,斐波那契数列的状态转移方程为 \(f_i = f_{i - 1} + f_{i - 2}\)

DP 如下:

f[1] = 1;
f[2] = 1;
for (int i = 3; i <= n; i++)f[i] = f[i - 1] + f[i - 2]; // 转移方程
cout << f[n];

同样的,我们可以将转移方程套用在递归暴力上:

int f(int n)
{if (n == 1 || n == 2)return 1;return f(n - 1) + f(n - 2); // 转移方程
}

动态规划要素

  1. 最优子结构:问题的最优解 包含 子问题最优解。即为:局部最优解 = 全局最优解。

  2. 无后效性

    • 在推导后面状态时,仅在意前面状态数值,不在意是如何推导出来的。

    • 某状态确定后,不会因为后面的决策而改变前面的决策。

  3. 重叠子问题:不同的决策到达相同的状态时可能产生重复的状态,为了避免不必要的计算,我们通常使用 记忆化搜索(在计算出新状态时将它存储起来一遍下次使用)来解决,这也是最经典的 空间换时间

不满足这三点你还想 DP?想 peach 呢?

状态的定义

前言:空间换时间

很简单的名字,即为使用空间的代价来确保不会超时。

状态?

状态,通俗来讲就是你 \(f_{xxx}\) 代表的是什么。比如斐波那契数列中 \(f_i\) 代表的就是第 \(i\) 为是什么。

对于状态:

  1. 状态越多,表示的信息越多,空间越大

  2. 反之,状态越少,表示的信息越少,空间越小

在我们状态定义时,可能有这些情况:

\(部分情况 \begin{cases} 状态太少?\begin{cases} 信息量太少 & 无解 \\\\ 信息量太少 & 不满足动态规划要素 \end{cases} \\\\ 状态太多? \begin{cases} 空间太大 & MLE \\\\ 需要太多时间更新状态 & TLE \end{cases} \end{cases}\)

所以,状态 and 状态转移方程时整个动态规划中最最最难的部分,想清楚这两点,这题也就解出来了。

参考资料

https://zh.wikipedia.org/wiki/动态规划

五大基本算法之动态规划算法 DP dynamic programming

动态规划解题套路框架 | labuladong 的算法笔记

1.最优子结构 | 数据结构与算法之美

例题

例题一思路

纯 DP

点我查看题目

没看数据:好一个 dfs!

注:两种情况

  1. 拿本物品

    • 3 倍奖金?

    • 1 倍奖金?

  2. 不拿本物品

ll dfs(int i, int now, ll cnt)
{if (i == n + 1)return cnt;if (!((now + 1) % 3) && ((now + 1) >= 3))return max(dfs(i + 1, now + 1, cnt + (a[i] * 3)), dfs(i + 1, now, cnt));elsereturn max(dfs(i + 1, now + 1, cnt + a[i]), dfs(i + 1, now, cnt));
}

我们看题面,一眼看出的状态为:\(f_i\) 表示前 \(i\) 个物品获得的最大奖金。

但是,我们发现不满足无后效性。

根据上述方法,我们尝试使用空间的代价来优化。

将状态改为:\(f_{i, j}\) 表示前 \(i\) 个物品,当前物品数取余 \(3\)\(j\) 时获得的最大奖金。

\(f{i, j} = \begin{cases} j = 0 \begin{cases} i \ge 3 \begin{cases} f_{i - 1, 0} & 不拿 \\\\ f_{i - 1, 2} + a_i \times 3 & 拿 \end{cases} \\\\ f_{i - 1, 0} & 没有到 3 个,不存在这种情况。 \end{cases} \\\\ j = 1 \begin{cases} f_{i - 1, 1} & 不拿 \\\\ f_{i - 1, 0} + a[i] & 拿 \end{cases} \\\\ j = 2 \begin{cases} i \ge 2 \begin{cases} f_{i - 1, 2} & 不拿 \\\\ f_{i - 1, 1} + a_i & 拿 \end{cases} \\\\ f_{i - 1, 2} & 没有至少 2 个物品,没有这种情况。 \end{cases} \end{cases}\)

完整代码为:

#include <bits/stdc++.h>
using namespace std;#define ll long long
int n;
ll a[100005];/*20PTS
ll dfs(int i, int now, ll cnt)
{if (i == n + 1)return cnt;if (!((now + 1) % 3) && ((now + 1) >= 3))return max(dfs(i + 1, now + 1, cnt + (a[i] * 3)), dfs(i + 1, now, cnt));elsereturn max(dfs(i + 1, now + 1, cnt + a[i]), dfs(i + 1, now, cnt));
}
*/ll f[100005][3];
ll ans;int main()
{cin >> n;for (int i = 1; i <= n; i++)cin >> a[i];// cout << dfs(1, 0, 0) << "\n";for (int i = 1; i <= n; i++){f[i][0] = f[i - 1][0];f[i][1] = f[i - 1][1];f[i][2] = f[i - 1][2];if (i >= 3)f[i][0] = max(f[i][0], f[i - 1][2] + (a[i] * 3));f[i][1] = max(f[i][1], f[i - 1][0] + a[i]);if (i >= 2)f[i][2] = max(f[i][2], f[i - 1][1] + a[i]);ans = max(ans, f[i][0]);ans = max(ans, f[i][1]);ans = max(ans, f[i][2]);}cout << ans << "\n";return 0;
}

点我查看题目

首先,我们欣赏一下原出题人的提示。

例题二前言:分类讨论

在看了许多不当人的讲解后,我浓缩出:分类讨论就是分类 --> 讨论!分类讨论就是将问题通过不同的结果 / 形式 / 不同点分成几类逐个解决。

例题二思路

既然说到分类讨论我们先来分个类。

\(\max(\sum_{i = 1}^{N} A_i) = \begin{cases} C > 0 & \max(\sum_{i = L}^{R} A_i) \times C \\\\ C < 0 & \min(\sum_{i = L}^{R} A_i) \times C \end{cases}\)

最大最小怎么使用 \(O(N)\) 求?Bingo!最大 / 最小 子段和即可。

最后比一下就好了。

完整 Code:

#include <bits/stdc++.h> 
using namespace std;#define ll long long
int n;
ll c;
ll a[100005];
ll solve()
{ll original_sum = 0;for (int i = 1; i <= n; ++i)original_sum += a[i];ll dp_max[100005], dp_min[100005];dp_max[1] = a[1];dp_min[1] = a[1];ll maxx = dp_max[1];ll minn = dp_min[1];for (int i = 2; i <= n; i++){dp_max[i] = max(a[i], dp_max[i - 1] + a[i]);dp_min[i] = min(a[i], dp_min[i - 1] + a[i]);maxx = max(maxx, dp_max[i]);minn = min(minn, dp_min[i]);}ll res = max((c - 1) * maxx, (c - 1) * minn);ll ans = original_sum + res;return ans;
}int main()
{cin >> n >> c;for (int i = 1; i <= n; ++i)cin >> a[i];cout << solve() << endl;return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/822784.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2024-2025-1 20241415 《计算机基础与程序设计》第五周学习总结

2024-2025-1 20241415《计算机基础与程序设计》第五周学习总结 作业信息这个作业属于哪个课程 2024-2025-1-计算机基础与程序设计这个作业要求在哪里 2024-2025-1计算机基础与程序设计第五周作业这个作业的目标 Pep/9虚拟机、机器语言与汇编语言、算法与伪代码、测试:黑盒,白…

第46篇 1.net学习avalonia开发-安装环境

1.安装avaloniaUI模版1.1cmd进入控制台,执行:dotnet new install Avalonia.Templates2.安装AvaloniaUI可视化设计工具 扩展-管理扩展(手动下载地址:https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaVS)然后关闭所有已经打开的vs进程后,自动安…

web端和pc端的区别

本文详细探讨了Web端和PC端应用的主要区别及其各自的特点和应用场景。文章内容包括:1.平台和运行环境;2.开发和维护要求;3.用户体验和交互设计;4.性能和资源占用;5.安全性和隐私;6.可访问性和便携性;7.市场趋势和用户偏好。理解这些区别对于软件开发者和终端用户在选择和…

Java NIO direct buffer的优势在哪儿

Java NIO Direct Buffer的优势主要体现在:减少数据在Java堆和本地堆之间的复制次数、提高了内存管理效率、可直接访问操作系统的内存资源、增加了处理大型数据集的能力。这些优势共同作用,显著提高了大数据量传输的性能。其中,提高内存管理效率尤为关键,因为它减轻了垃圾收…

2024-2025-1 20241314 《计算机基础与程序设计》第五周学习总结

2024-2025-1 20241314 《计算机基础与程序设计》第五周学习总结 作业信息这个作业属于哪个课程 2024-2025-1-计算机基础与程序设计这个作业要求在哪里 2024-2025-1计算机基础与程序设计第五周作业这个作业的目标 Pep/9 虚拟机 机器语言与汇编语言 算法与伪代码 测试:黑盒,白…

第四次高级程序语言设计

2024高级语言程序设计:https://edu.cnblogs.com/campus/fzu/2024C 高级语言程序设计课程第四次个人作业:https://edu.cnblogs.com/campus/fzu/2024C/homework/13293 学号:102400115 姓名:洪育豪 6.16.1问题:无6.16.5问题:忘记将字符转成对应的ASCII值 导致不能输出 解决:检查…

抖音发送私信接口响应的二进制数据解析

请求发送评论接口得到: data=bap`\x00 \x97\xce\xaa(\xef\xaa\xcd\x98[\xe1\x07\xcex\xd3%\xa4\x06z\x07$N\x12c\xde\x9b\xf0\xb2\xff \xb6&\xcb\xce\xfc\xd5~\xbf\xd0=\x94 \x1e\xda\x9e|\xc7\xfcED\xf4\xeePI.\xc94\x99G\xb1D\xc8d\xf1f\x9e %\x12D\x9dN\x88\x84\xd2x,\…

fbprophet时序模型和LSTM有什么优劣么

fbprophet时序模型优点:1、易用性;2、灵活性;3、内置节假日效应等。fbProphet缺点:1、简单性;2、依赖性。LSTM的优势:1、学习复杂模式;2、适用于各自数据;3、模型调整。LSTM的劣势:1、计算复杂;2、难以理解;3、过拟合风险。LSTM模型通常需要更多的计算资源和时间来训…

如何在Linux上优化系统性能

​优化Linux系统性能需要遵循以下关键步骤:1. 识别和监控系统瓶颈;2. 最大化硬件资源使用;3. 优化内核参数和服务设置;4. 调整文件系统和存储性能;5. 选择合适的软件和工具。优化的第一步是通过监控工具了解系统的当前状态。1.识别和监控系统瓶颈 优化Linux系统性能的开始…

Java中ThreadLocal的实际用途是啥_1

### 开篇回答 Java中的ThreadLocal主要用于提供线程局部变量,这些局部变量不同于普通的变量,它们只在各自线程中可见、共享。ThreadLocal 的实际用途主要包括:保持数据库连接、保障 SimpleDateFormat 的线程安全、进行性能监控、传递上下文信息等。尤其是在多线程环境下,确…

球坐标下的 Laplace 算子推导

本文从球坐标、拉梅系数、正交曲线坐标系、梯度和 Nabla 算子、散度和 Laplace 算子,一步一步进行介绍和推导,最终得到在球坐标系下的 Laplace 算子的表达式。球坐标下的 Laplace 算子推导Ciallo~(∠・ω< )⌒★ 我是赤川鹤鸣!在学习球谐函数的时候,第一次听说球坐标下…

对通用骨架提取方法的改进

01 骨架提取的任务引入 骨架(或中轴线)具有在二进制形状和自然图像中提供紧凑而有意义的对象表示的潜力(以下简称为“形状”和“图像”),适用于图像表示和各种多媒体应用。在实践中,对象骨架通常以图形格式编码,即“骨架图”,以便于骨架修剪、匹配、分类和分析任务。为…