★动态规划(DP算法)详解

2023-11-15

什么是动态规划:动态规划_百度百科

内容太多了不作介绍,重点部分是无后效性,重叠子问题,最优子结构。

d6eac15165f240f8999e08b97665cf8f.png问S->P1和S->P2有多少种路径数,毫无疑问可以先从S开始深搜两次,S->P1和S->P2找出所有路径数,但是当这个图足够大,那就会超时。

动态规划旨在用空间换时间,我们可以发现S->P2的路上,实际上重复计算了S->P1,然后再去计算P1->P2,如果我们第一次计算S->P1的时候,保留了P1点的路径数,那么就不用再次计算S->P1了。

无后效性:未来的状态不会影响过去的状态,如果我在P1->P2的时候,S->P1多了一条路出来,那么先前保留的路径数就是错误的。

tip:下面的例题讲解并不是特别好,还未修正,建议先拉到最下面看20230504 Update.


经典的数塔问题也是dp算法的入门问题之一

假设你有这么一个数塔,你的目标是求最底层到最高层,求出最大路径和

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAenpj5aSn6a2U546L,size_20,color_FFFFFF,t_70,g_se,x_16

比如3->7->2->9这个路径,他的路径和是3+7+2+9

不难发现如果要求到9的最大路径和,首先要求出他前一层的最大路径

核心代码dp[i][j]=max(dp[i-1][j],dp[i-1][j+1])+a[i][j]

dp[i][j](9的最大路径和)

a[i][j](9自己)

dp[i-1][j](前一层5的最大路径和)dp[i-1][j+1](前一层2的最大路径和)

在前一层的最大路径和取大的那一个


例题:[NOIP2005 普及组] 采药 - 洛谷

e56aa3aa628848259cb937a08de76309.png

f843d45d8cde4de7b4ea4d60108828e2.png

这道题输入这么少也不用scanf了,直接上cin加上小优化

inline void scan(){
    ios::sync_with_stdio(false);//解除与scanf和cout的同步,具体体现在缓冲区
    cin.tie(nullptr),cout.tie(nullptr);//可以加快一点速度
    cin>>T>>m;
    for(int i=1;i<=m;++i)
        cin>>t[i]>>v[i];
}

很明显就是各个最优子问题的问题,要取到最多的价值,必然前一个状态也是最多的。

所以考虑定义问题的全部基础属性,每个草药有价值和对应所需的时间,目标是最大价值。

作出以下定义

定义dp[i][j]为采前i朵,消耗时间j内的最大价值
    
不难得出以下两种情况
1.当前时间可以采走这支草药
  (1)如果要采这支采药,那先前状态就要预留j-t[i]时间来满足定义
  (2)如果不采那最大价值就是先前状态dp[i-1][j]
  因为不清楚哪一种情况更好,但是我们只需要最大值,所以max
  综上dp[i][j]=max(dp[i-1][j],dp[i-1][j-t[i]]+v[i]);

2.当前时间不够采走这支草药,采不了那就不采,继承先前状态.  
  即dp[i][j]=d[i-1][j]

或者说延续用上文的想法,n支草药价值最大我不知道
如果只有1支草药呢?,那我是不是就知道了
1支草药知道了,那我2支草药是不是也知道了

然后...直接从基础状态循环到结束,即从第一支草药开始循环。

显而易见不能从采最后几朵开始往前判断,前面状态都不清楚,怎么可以从后面开始。

AC代码

#include <bits/stdc++.h>
using namespace std;

int T,m,t[101],v[101],dp[101][1001];
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr),cout.tie(nullptr);
    cin>>T>>m;
    for(int i=1;i<=m;++i)
        cin>>t[i]>>v[i];
    for(int i=1;i<=m;++i){
        for(int j=1;j<=T;++j){
            if(j>=t[i])dp[i][j]=max(dp[i-1][j],dp[i-1][j-t[i]]+v[i]);
            else dp[i][j]=dp[i-1][j];
        }
    }
    cout<<dp[m][T];
    return 0;
}

这道题还可以接着优化,因为不难发现dp[][]的一维空间始终是在dp[i]和dp[i-1]范围内
也就是说我们其实可以舍弃这一维来节约很大范围的空间。

dp代码

    for(int i=1;i<=m;++i)
        for(int j=T;j>=t[i];j--)
            dp[j]=max(dp[j-t[i]]+v[i],dp[j]);

内部循环必须从大到小,因为先前是应用了i-1先前状态去得出i状态,但是此处舍去了一维之后就会导致两者状态都会出现在这个小小的一维数组里面。

7f69dc1f7a434ace919173e82497844c.png

比如说用线段来表示所有情况,蓝色的得出了红色的状态。

但是如果我只剩下了一条线段呢?

b986bee879174438a3cae9d4298954ec.png

 如果中间被前面先修改了,那么后面要更新状态的时候用的就是红色,而不是我们所需要的蓝色。

所以只能从后往前去推状态才能保证我们所需要的一直是蓝色,而不会被更新成红色。


发现了吧,重点在于找出继承状态(递推式),比如定义的是前n个人,而不是任意n个人,这样n-1和n的区别就在于多了一个人,只要让先前状态抽出能满足多一个人的情况,那就是后者的状态。

例题:5 倍经验日 - 洛谷

b0782c7e2caf480cae6f7f3ecf7dd4ec.png

09856a1b036949339caa969f3486327e.png

 和上面那道题基本没有什么区别,定义所有相关的基础态

定义dp[i][j]为前i个人 用j个药 可以获得的最大经验值

然后就可以得出递推式

    for(int i=1;i<=n;++i)
        for(int j=1;j<=x;++j)
            if(j>=use[i])
                dp[i][j]=max(dp[i-1][j]+lose[i],dp[i-1][j-use[i]]+win[i]);
            else
                dp[i][j]=dp[i-1][j]+lose[i];

如果能打过,那么我可以选择打或者不打
如果打不过,那只能不打

但是这道题有一个注意点是,J可以从0开始,因为存在不用药就可以打过的情况,所以先初始化不用药的情况。

初始化

    for(int i=1;i<=n;++i)
        if(use[i]==0)
            dp[i][0]=dp[i-1][0]+win[i];
        else
            dp[i][0]=dp[i-1][0]+lose[i];

AC代码

#include <bits/stdc++.h>
using namespace std;
const int MAXN=1e3+1;

long long n,x,win[MAXN],lose[MAXN],use[MAXN],dp[MAXN][MAXN];
//dp[i][j]前i个人,使用j个药,能获得的最大经验值
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr),cout.tie(nullptr);
    cin>>n>>x;
    for(int i=1;i<=n;++i){
        cin>>lose[i]>>win[i]>>use[i];
    }

    for(int i=1;i<=n;++i)
        if(use[i]==0)
            dp[i][0]=dp[i-1][0]+win[i];
        else
            dp[i][0]=dp[i-1][0]+lose[i];
    for(int i=1;i<=n;++i)
        for(int j=1;j<=x;++j)
            if(j>=use[i])
                dp[i][j]=max(dp[i-1][j]+lose[i],dp[i-1][j-use[i]]+win[i]);
            else
                dp[i][j]=dp[i-1][j]+lose[i];
    cout<<dp[n][x]*5;
    return 0;
}
很明显这道题i和i-1也是滚动使用的
不过n的范围不是很大,所以不考虑优化

例题:疯狂的采药 - 洛谷

和上上题采药的区别就是,每个药可以无限采

每条线的含义都是一样的,也就是每个药都只能采一次。

7f69dc1f7a434ace919173e82497844c.png

这道题每个药都可以无限采,也就是说同一行之间也要去迭代所有情况

78ba15c6071344c0b40afea0ec02af41.png

上上题里面发现了,一维dp一定要逆序时间才能得出不迭代自己的状态,那么这道题正序迭代即可。

AC代码

#include <bits/stdc++.h>
using namespace std;
const int MAXM=1e4+1;
const int MAXT=1e7+1;

int T,m,t[MAXM],v[MAXM];
long long dp[MAXT];

int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr),cout.tie(nullptr);
    cin>>T>>m;
    for(int i=1;i<=m;++i){
        cin>>t[i]>>v[i];
    }
    for(int i=1;i<=m;++i){
        for(int j=t[i];j<=T;++j){
            dp[j]= max(dp[j],dp[j-t[i]]+v[i]);
        }
    }
    cout<<dp[T];
    return 0;
}

j一定要从t[i]开始,不然下标要越界了。

如果要写出二维dp首先要改变定义,因为一个是只能用一次,一个是无限用,递推式必然不一样

定义dp[i][j]为采前i朵(无限采),消耗时间j内的最大价值
    
不难得出以下两种情况
1.当前时间可以采走这支草药
  (1)继承先前状态
  (2)迭代自己
  综上dp[i][j]=max(dp[i-1][j],dp[i][j-t[i]]+v[i]);
                                注意此处是i,不是i-1

2.当前时间不够采走这支草药,采不了那就不采,继承先前状态.  
  即dp[i][j]=d[i-1][j]

20230504 Update.

在隐隐约约或者分析出当前做的题目是dp题目的时候,dp题目的做法可以采取以下两种方式。

第一种:

1. 分析题目,提取关键。

2. 用数学语言表达问题。

3. 定状态转移方程。

4. 初始化。

5. 循环求解。

第二种:

1. 直接分析答案可能和哪些属性有关联。

2. 定状态转移方程。

3. 初始化。

4. 循环求解。

如果能用第一种分析出来那肯定是最好的,有了数学公式干什么都方便。

看例题。

[NOIP2012 普及组] 摆花 - 洛谷

因为本蒟蒻是蒟蒻(叉腰),所以直接用第二种方法。

可以发现摆花方案只和下面几种属性有关:
1. 花的种类

2. 花的数量,以及总数要小于一个限定数 -> 花的总数

3. 花的顺序

尝试做出定义 f[i][j] 为用上前 i 种花,且到当前为止已经用了 j 盆花的所有方案数。

当我们正序迭代这个式子的时候,可以发现 3. 花的顺序 这个属性被隐含解决了。

即这个定义目前看来还是可行的。

根据定义易得:f[i][j]=\sum_{k=j-a[i]}^j{f[i-1][k]},其中 1<=j<=m (不管他到现在最多能用多少,直接暴力枚举)。

初始化则为一种花都不用,一盆花都没有,即 f[i][0]=1 。其中 0<=i<=n 。

出现了 k=j-a[i] 所以循环的时候要注意下标越界,判断 k>=0 。

AC代码

#include <bits/stdc++.h>
using namespace std;
const int MOD=1e6+7,N=101;

int n,m,f[N][N],a[N];

int main(){
    cin>>n>>m;
    for(int i=1;i<=n;++i)
        cin>>a[i];

    for(int i=0;i<=n;++i)
        f[i][0]=1;
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
            for(int k=j-a[i];k<=j;++k)
                if(k>=0)f[i][j]=(f[i][j]+f[i-1][k])%MOD;
    cout<<f[n][m];
    return 0;
}

例题:[NOIP2008 普及组] 传球游戏 - 洛谷

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

★动态规划(DP算法)详解 的相关文章

  • 在 C++ 中分割大文件

    我正在尝试编写一个程序 该程序接受一个大文件 任何类型 并将其分成许多较小的 块 我想我已经有了基本的想法 但由于某种原因我无法创建超过 12 kb 的块大小 我知道谷歌等上有一些解决方案 但我更感兴趣的是了解这个限制的根源是什么 然后实际
  • 如何进行带有偏差的浮点舍入(始终向上或向下舍入)?

    我想以偏置舍入浮动 要么总是向下 要么总是向上 代码中有一个特定的点 我需要这个 程序的其余部分应该像往常一样四舍五入到最接近的值 例如 我想四舍五入到最接近的 1 10 倍数 最接近 7 10 的浮点数约为 0 69999998807 但
  • 捕获 foreach 条件中抛出的异常

    我有一个foreach在 foreach 本身的条件下循环期间中断的循环 有没有办法try catch抛出异常然后继续循环的项 这将运行几次 直到异常发生然后结束 try foreach b in bees exception is in
  • 处理 fanart.tv Web 服务响应 JSON 和 C#

    我正在尝试使用 fanart tv Webservice API 但有几个问题 我正在使用 Json Net Newtonsoft Json 并通过其他 Web 服务将 JSON 响应直接反序列化为 C 对象 这里的问题是元素名称正在更改
  • 有什么工具可以说明每种方法运行需要多长时间?

    我的程序的某些部分速度很慢 我想知道是否有我可以使用的工具 例如它可以告诉我可以运行 methodA 花了 100ms 等等 或者类似的有用信息 如果您使用的是 Visual Studio Team System 性能工具 中有一个内置分析
  • Linux TUN/TAP:无法从 TAP 设备读回数据

    问题是关于如何正确配置想要使用 Tun Tap 模块的 Linux 主机 My Goal 利用现有的路由软件 以下为APP1和APP2 但拦截并修改其发送和接收的所有消息 由Mediator完成 我的场景 Ubuntu 10 04 Mach
  • ZLIB 解压缩

    我编写了一个小型应用程序 该应用程序应该解压缩以 gzip deflate 格式编码的数据 为了实现这一点 我使用 ZLIB 库 使用解压缩功能 问题是这个功能不起作用 换句话说 数据不是未压缩的 我在这里发布代码 int decompre
  • 获取从属性构造函数内部应用到哪个属性的成员?

    我有一个自定义属性 在自定义属性的构造函数内 我想将属性的属性值设置为属性所应用到的属性的类型 是否有某种方式可以访问该属性所应用到的成员从我的属性类内部 可以从 NET 4 5 using CallerMemberName Somethi
  • C++11 函数局部静态 const 对象的线程安全初始化

    这个问题已在 C 98 上下文中提出 并在该上下文中得到回答 但没有明确说明有关 C 11 的内容 const some type create const thingy lock my lock some mutex static con
  • 单元测试失败,异常代码为 c0000005

    我正在尝试使用本机单元测试项目在 Visual Studios 2012 中创建单元测试 这是我的测试 TEST METHOD CalculationsRoundTests int result Calculations Round 1 0
  • 为什么 FTPWebRequest 或 WebRequest 通常不接受 /../ 路径?

    我正在尝试从 ftp Web 服务器自动执行一些上传 下载任务 当我通过客户端甚至通过 Firefox 连接到服务器时 为了访问我的目录 我必须指定如下路径 ftp ftpserver com AB00000 incoming files
  • 组合框项目为空但数据源已满

    将列表绑定到组合框后 其 dataSource Count 为 5 但组合框项目计数为 0 怎么会这样 我习惯了 Web 编程 而且这是在 Windows 窗体中进行的 所以不行combo DataBind 方法存在 这里的问题是 我试图以
  • C# 创建数组的数组

    我正在尝试创建一个将使用重复数据的数组数组 如下所示 int list1 new int 4 1 2 3 4 int list2 new int 4 5 6 7 8 int list3 new int 4 1 3 2 1 int list4
  • 从匿名类型获取值

    我有一个方法如下 public void MyMethod object obj implement 我这样称呼它 MyMethod new myparam waoww 那么我该如何实施MyMethod 获取 myparam 值 Edit
  • 过期时自动重新填充缓存

    我当前缓存方法调用的结果 缓存代码遵循标准模式 如果存在 则使用缓存中的项目 否则计算结果 在返回之前将其缓存以供将来调用 我想保护客户端代码免受缓存未命中的影响 例如 当项目过期时 我正在考虑生成一个线程来等待缓存对象的生命周期 然后运行
  • 32位PPC rlwinm指令

    我在理解上有点困难rlwinmPPC 汇编指令 旋转左字立即然后与掩码 我正在尝试反转函数的这一部分 rlwinm r3 r3 0 28 28 我已经知道什么了r3 is r3在本例中是一个 4 字节整数 但我不确定这条指令到底是什么rlw
  • 内核开发和 C++ [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 从我know https stackoverflow com questions 580292 what languages are windo
  • 以编程方式使用自定义元素创建网格

    我正在尝试以编程方式创建一个网格 并将自定义控件作为子项附加到网格中 作为 2x2 矩阵中的第 0 行第 0 列 为了让事情变得更棘手 我使用了 MVVM 设计模式 下面是一些代码可以帮助大家理解这个想法 应用程序 xaml cs base
  • 热重载时调用方法

    我正在使用 Visual Studio 2022 和 C 制作游戏 我想知道当您热重新加载应用程序 当它正在运行时 时是否可以触发一些代码 我基本上有 2 个名为 UnloadLevel 和 LoadLevel 的方法 我想在热重载时执行它
  • 如何确定母版页中正在显示哪个子页?

    我正在母版页上编写代码 我需要知道正在显示哪个子 内容 页面 我怎样才能以编程方式做到这一点 我用这个 string pageName this ContentPlaceHolder1 Page GetType FullName 它以 AS

随机推荐

  • Go语言入门【10】Map

    Map map是一种键值对形式的数据结构 一个键对应一个值 可以通过键快速检索出其对应的value值 在map中key的值是唯一的 value的值不唯一 并且map中保存的数据是无序的 Map声明 声明Map可以使用map关键字进行声明 同
  • 【华为OD机试真题 JS】连续出牌数量

    标题 连续出牌数量 时间限制 1秒 内存限制 262144K 语言限制 不限 有这么一款单人卡牌游戏 牌面由颜色和数字组成 颜色为红 黄 蓝 绿中的一种 数字为0 9中的一个 游戏开始时玩家从手牌中选取一张卡牌打出 接下来如果玩家手中有和他
  • 核磁共振重建算法综述

    自己整理的一些核磁共振重建综述文章 仅供参考 不发表 文章目录 摘要 一 引言 二 核磁共振成像原理 2 1 核磁共振成像过程 2 2 K空间 2 3 核磁共振数据采集过程 三 核磁共振重建方法 3 1 部分傅里叶重建方法 3 2 并行重建
  • 毕业设计—基于深度学习的网络流量预测研究

    目录 前言 课题背景和意义 实现技术思路 一 网络流量预测评价指标 二 基于深度学习的网络流量预测方法 实现效果图样例 最后 前言 大四是整个大学期间最忙碌的时光 一边要忙着备考或实习为毕业后面临的就业升学做准备 一边要为毕业设计耗费大量精
  • 【微信公众号】微信公众号授权出现的常见问题解决方案

    问题1 在微信公众号授权时出现了 解决方案 1 首先查看后端的url配置是否正确 是否进行了转码 官方API上说明了redirectUrl应使用String redirectUri URLEncoder encode redirectUrl
  • 记一次VUE项目中内存崩溃的排查

    F12 内存工具查看内存情况 大量的数据被 vue 代理 UI 框架内存溢出
  • 第七课:变量的高级主题(下)

    变量的高级主题 下 环境变量 全局变量 makfile中能够直接使用环境变量的值 定义了同名变量 环境变量将被覆盖 运行make时指定 e 选项 优先使用环境变量 为什么要在makefile中使用环境变量 优势 环境变量可以在所有的make
  • 【Attention】Dual Attention(DANet) & Fully Attention(FLA)

    空间注意力有助于保留细节信息 通道注意力有助于保留大物体的语义一致性 有效使用两种注意力可以提升性能 本文旨在记录一些常用的注意力 以及代码实现 包括两篇文章 DANet FLA Dual Attention Network for Sce
  • linux笔记之初次接触信号

    一 关于信号概念 1 信号是Linux所使用的进程间通信的最古老的方式 它是在软件层次上对中断机制的一种模拟 是一种异步通信的方式 一个完整的信号周期包括三个部分 信号的产生 信号在进程中的注册 信号在进程中的注销 执行信号处理函数 如下图
  • Linux系统之部署Dailynotes个人笔记管理工具

    Linux系统之部署Dailynotes个人笔记管理工具 一 Dailynotes介绍 二 本地环境介绍 2 1 本地环境规划 2 2 本次实践介绍 三 检查本地环境 3 1 检查本地操作系统版本 3 2 检查系统内核版本 3 3 检查本地
  • Python中insert用法详解!

    Python中insert用法是什么 这篇文章为大家详细的讲解一下Python中insert用法 并附带实战案例 希望能够给你们带来帮助 描述 insert 函数用于将指定对象插入列表的指定位置 语法 inser 方法语法 list ins
  • 华为云云耀云服务器L实例评测|Linux系统之安装Tomcat

    华为云云耀云服务器L实例评测 Linux系统之安装Tomcat 一 云耀云服务器L实例介绍 1 1 云耀云服务器L实例简介 1 2 云耀云服务器L实例特点 二 Tomcat介绍 2 1 Tomcat简介 2 2 Tomcat特点 三 本次实
  • 可视化技巧:分类问题中的决策面画法 (直观理解plt.contour的用法)

    摘要 通过分类问题中决策面的绘制过程直观理解matplotlib中contour的用法 主要包括对 np meshgrid 和plt contour的直观理解 前言 分类问题中 我们习惯用2维的dmeo做例子 验证算法的有效性 直观的评价方
  • css实现随机颜色,CSS3 一个显示随机颜色的动画

    CSS 语言 CSSSCSS 确定 html body background webkit linear gradient top fff dcf background linear gradient to bottom fff dcf h
  • upload-labs-master靶场 Pass06-10通关秘诀(详解版)

    关数 通关特征 PASS 06 大小写绕过上传 关卡分析 有些程序编写上传点过滤时会过滤常见后缀 黑名单 如php asp aspx jsp phtml等 如果为对上传 后缀进行小写转换 那么我们即可通过文件后缀名大小写方式进行绕过上传we
  • 数据结构之线性结构

    数据结构是计算机存储 组织数据的方式 数据结构是指相互之间存在一种或多种特定关系的数据元素的集合 通常情况下 精心选择的数据结构可以带来更高的运行或者存储效率 数据结构往往同高效的检索算法和索引技术有关 常见的数据结构可分为 线性结构 树形
  • 用docker借助deepo镜像训练深度学习模型

    这里写自定义目录标题 用docker借助deepo镜像训练深度学习模型 Deepo简介 docker hub 地址 安装步骤 运行 用docker借助deepo镜像训练深度学习模型 Deepo简介 deepo是我从网上了解的一个比较全的深度
  • 回想过去几年的编程生活

    17年 我从一所普通的二本学校毕业 抱着对未来无限憧憬的希望踏上社会 3年初中 3年高中 4年大学 一步一步 努力奋斗 终于要开始挣钱了 终于可以独立了 仿佛美好的一切都会来的 记得是16年的是12月 大四上学期 结束 我迫不及待的找了一份
  • (实习)基线检测时遇到的问题

    首先要先清楚什么是基线检测 安全基线其实是系统最低安全要求的配置 常见的安全基线配置标准有ISO270001 等级保护2 0等 企业也可以建立自己的标准 检测的内容 分为三个方面 1 系统存在的安全漏洞 2 系统配置的脆弱性 3 系统状态的
  • ★动态规划(DP算法)详解

    什么是动态规划 动态规划 百度百科 内容太多了不作介绍 重点部分是无后效性 重叠子问题 最优子结构 问S gt P1和S gt P2有多少种路径数 毫无疑问可以先从S开始深搜两次 S gt P1和S gt P2找出所有路径数 但是当这个图足