C++动态规划超详细总结

news2025/1/12 23:08:31

动态规划

首先来介绍一下动态规划,但我不想用过于官方的语言来介绍。动态规划是一种思想,它常用于最优解问题(即所有问题包括所有子问题的解为最优解),它有点像递推,是在已知问题的基础上解决其他问题。这种思想较为复杂,也是很多 OIer 的痛。

解题步骤

  1. 把一个问题拆分成很多小问题

  1. 找出最初的状态(即上文“在已知问题的基础上”的已知部分)

  1. 建立状态转移方程(即上文“解决其他问题”)

其实状态转移方程有点像找规律,通过前面的规律推出后面。

例题讲解

我们先从最简单经典的跳台阶问题开始。

台阶问题

题目描述

有 N 级的台阶,你一开始在底部,每次可以向上迈最多K级台阶(1或2级),问到达第N级台阶有多少种不同方式。

输入格式

两个正整数N,K。

输出格式

一个正整数,为不同方式数。

样例 #1

样例输入 #1

5 2

样例输出 #1

8

台阶问题的解法

思路

首先题目的意思就是 N 阶台阶,每次可以迈 1或2 阶,问有几种迈的方法。

这里我们不妨设一个函数 为结果。

每阶台阶可以向上走 1或2阶,那么第 N 阶台阶一定是从 N-1 或者 N-2 阶台阶来的,第 N-1 或 N-2 阶台阶也一定是从 N-3/N-2 或 N-3/N-4 来的,以此类推。

那么,状态转移方程为

dp[N]=dp[N-1]+dp[N-2]

怎么样,是不是很简单?

难度提升!

代码

#include<iostream>
using namespace std;
int m,dp[3],n;
int main(){
  cin >> n;
  for(int i=1;i<=n;i++){
    cin >> m;
    dp[0]=1;
    dp[1]=1;
    if(m<2) break;
    for(int j=2;j<m;j++){
      dp[j]=dp[j-1]+dp[j-2];
    }
    cout << dp[m-1] << endl;
  }
  return 0;
}

田忌赛马

题目描述

我国历史上有个著名的故事: 那是在2300年以前。齐国的大将军田忌喜欢赛马。他经常和齐王赛马。他和齐王都有三匹马:常规马,上级马,超级马。一共赛三局,每局的胜者可以从负者这里取得200银币。每匹马只能用一次。齐王的马好,同等级的马,齐王的总是比田忌的要好一点。于是每次和齐王赛马,田忌总会输600银币。

田忌很沮丧,直到他遇到了著名的军师――孙膑。田忌采用了孙膑的计策之后,三场比赛下来,轻松而优雅地赢了齐王200银币。这实在是个很简单的计策。由于齐王总是先出最好的马,再出次好的,所以田忌用常规马对齐王的超级马,用自己的超级马对齐王的上级马,用自己的上级马对齐王的常规马,以两胜一负的战绩赢得200银币。实在很简单。

如果不止三匹马怎么办?这个问题很显然可以转化成一个二分图最佳匹配的问题。把田忌的马放左边,把齐王的马放右边。田忌的马A和齐王的B之间,如果田忌的马胜,则连一条权为200的边;如果平局,则连一条权为0的边;如果输,则连一条权为-200的边……如果你不会求最佳匹配,用最小费用最大流也可以啊。 然而,赛马问题是一种特殊的二分图最佳匹配的问题,上面的算法过于先进了,简直是杀鸡用牛刀。现在,就请你设计一个简单的算法解决这个问题。

输入格式

第一行一个整数n,表示他们各有几匹马(两人拥有的马的数目相同)。第二行n个整数,每个整数都代表田忌的某匹马的速度值(0 <= 速度值<= 100)。第三行n个整数,描述齐王的马的速度值。两马相遇,根据速度值的大小就可以知道哪匹马会胜出。如果速度值相同,则和局,谁也不拿钱。

数据规模

对于20%的数据,1<=N<=65;

对于40%的数据,1<=N<=250;

对于100%的数据,1<=N<=2000。

输出格式

仅一行,一个整数,表示田忌最大能得到多少银币。

样例 #1

样例输入 #1

3
92 83 71
95 87 74

样例输出 #1

200

田忌赛马问题的解法

这道题除了 DP ,还有简单的做法,我直接放代码,但为了学习 DP,我还是讲一下 DP 做法。

简单解法

//田忌赛马
#include<iostream>
#include<algorithm>
using namespace std;
int n,qsp[2010],tsp[2010];
int main(){
    cin>>n;
    for(int i=0;i<n;i++){
        cin>>tsp[i];
    }
    for(int i=0;i<n;i++){
        cin>>qsp[i];
    }
    sort(qsp,qsp+n);
    sort(tsp,tsp+n);
    int tmin=0,tmax=n-1,qmin=0,qmax=n-1,jb=0;
    for(int i=0;i<n;i++){
        if (tsp[tmin]>qsp[qmin]){
            jb+=200;
            tmin++;
            qmin++;
        }
        else if(tsp[tmax]>qsp[qmax]){
            jb+=200;
            tmax--;
            qmax--;
        }
        else if(tsp[tmin]<qsp[qmax]){
            jb-=200;
            qmax--;
            tmin++;
        }
    }
    cout<<jb<<endl;
    return 0;
}

这段代码大家应该能看懂,我不做讲解。

DP 做法

看到这道题,大家可能毫无头绪(做题时不要损坏设备)

首先,田忌拥有比赛的“主动权”,因为他可以根据齐王出的马来出马。可以假设齐王出马的顺序是从强到弱,那么田忌出马应该是最强或最弱。用 f[i,j] 表示齐王出了 i 匹较强的马和田忌出了 j 匹较强的马。i-j 表示较弱的马比赛之后田忌获得的利益。

那么状态转移方程是

f[i][j]=max(f[i-1][j]+g[n-i+j+1][i],f[i-1][j-1]+g[j][i])

其中g[i][j] 表示田忌的马和齐王的马分别按照由强到弱的顺序排序之后,田忌的第 i 匹马和齐王的第 j 匹马赛跑所能取得的盈利,胜为 200 ,负为 −200 ,平为 0。

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

const int N=2001,INF=-2e+8;
int a[N],b[N],g[N][N],f[N];

bool Cmp(int n1,int n2) {return n1>n2;}

int main()
{
    int n,Ans,i,j; scanf("%d",&n);
    for (i=1;i<=n;++i) scanf("%d",&a[i]);
    for (i=1;i<=n;++i) scanf("%d",&b[i]);
    sort(a+1,a+n+1,Cmp),sort(b+1,b+n+1,Cmp);
    for (i=1;i<=n;++i)
        for (j=1;j<=n;++j)
        {
            if (a[i]>b[j]) g[i][j]=200;
            else if (a[i]==b[j]) g[i][j]=0;
                 else g[i][j]=-200;
        }
    for (i=1;i<=n;++i) f[i]=INF;
    for (i=1;i<=n;++i)
    {
        f[i]=f[i-1]+g[i][i];
        for (j=i-1;j>0;--j)
            f[j]=max(f[j]+g[n-i+j+1][i],f[j-1]+g[j][i]);
        f[0]=f[0]+g[n-i+1][i];
    }
    Ans=f[1];
    for (i=2;i<=n;++i) Ans=max(Ans,f[i]);
    printf("%d\n",Ans);
    return 0;
}

怎么样,还能理解吗?

[ 真题 ] 纪念品

动态规划的难度和精髓在于状态转移方程。 ——鲁迅(我没说过这句话)

接下来这道题会让大家知道什么是真正的状态转移方程。

题目描述

小伟突然获得一种超能力,他知道未来 TN 种纪念品每天的价格。某个纪念品的价格是指购买一个该纪念品所需的金币数量,以及卖出一个该纪念品换回的金币数量。

每天,小伟可以进行以下两种交易无限次

1. 任选一个纪念品,若手上有足够金币,以当日价格购买该纪念品;

2. 卖出持有的任意一个纪念品,以当日价格换回金币。

每天卖出纪念品换回的金币可以立即用于购买纪念品,当日购买的纪念品也可以当日卖出换回金币。当然,一直持有纪念品也是可以的。

T 天之后,小伟的超能力消失。因此他一定会在第 T 天卖出所有纪念品换回金币。

小伟现在有 M 枚金币,他想要在超能力消失后拥有尽可能多的金币。

输入格式

第一行包含三个正整数 T, N, M,相邻两数之间以一个空格分开,分别代表未来天数 T,纪念品数量 N,小伟现在拥有的金币数量 M

接下来 T 行,每行包含 N 个正整数,相邻两数之间以一个空格分隔。第 i 行的 N 个正整数分别为 P_{i,1},P_{i,2},……,P_{i,N},其中 P_{i,j} 表示第 i 天第 j 种纪念品的价格。

输出格式

输出仅一行,包含一个正整数,表示小伟在超能力消失后最多能拥有的金币数量。

样例 #1

样例输入 #1

6 1 100
50
20
25
20
25
50

样例输出 #1

305

样例 #2

样例输入 #2

3 3 100
10 20 15
15 17 13
15 25 16

样例输出 #2

217

提示

【输入输出样例 1 说明】

最佳策略是:

第二天花光所有 100 枚金币买入 5 个纪念品 1;

第三天卖出 5 个纪念品 1,获得金币 125 枚;

第四天买入 6 个纪念品 1,剩余 5 枚金币;

第六天必须卖出所有纪念品换回 300 枚金币,第四天剩余 5 枚金币,共 305 枚金币。

超能力消失后,小伟最多拥有 305 枚金币。

【输入输出样例 2 说明】

最佳策略是:

第一天花光所有金币买入 10 个纪念品 1;

第二天卖出全部纪念品 1 得到 150 枚金币并买入 8 个纪念品 2 和 1 个纪念品 3,剩余 1 枚金币;

第三天必须卖出所有纪念品换回216 枚金币,第二天剩余1枚金币,共 217 枚金币。

超能力消失后,小伟最多拥有 217 枚金币。

纪念品问题的解法

思路

这道题其实是动态规划和完全背包问题的结合。

我们进行 t−1 轮完全背包:

把今天手里的钱当做背包的容量

把商品今天的价格当成它的消耗,

把商品明天的价格当做它的价值

每一天结束后把总钱数加上今天赚的钱,直接写背包模板即可。

另: 在这道题中,我们可以把商品和钱看成同样的东西,因为题目中说了:可以当天买当天卖,所以不必考虑跨天的买卖,只需考虑当天的即可,这满足动态规划对于最优化原理和无后效性的要求,可以大胆地购买。

除第一天只有购入过程、最后一天只有售出过程外,每天都有售出与购入两个过程。两个过程互不干扰。

为获得更多的“资金”,不妨令每日的售出过程先于购入过程。

每天的购入过程与次日的售出过程(差价)构成一次完全背包。或者说,完全背包是在“第 X.5 天”进行的。

定义:

f[i]为用 i 元钱去购买商品所能盈利的最大值(不含成本

状态转移方程: f[j]=max(f[j],f[jprice[i][k]]+price[i][k+1]−price[i][k]);

代码

#include <iostream>
#include <memory.h>
using namespace std;
const int N = 101;
const int M = 10001;
int n, m, t, price[N][N], f[M];
int main()
{
    cin >> t >> n >> m;
    for(int i = 1; i <= t; i++)
        for(int j = 1; j <= n; j++)
            cin >> price[j][i];
          //读入每种商品每天的价格
    for(int k = 1; k < t; k++)
    {
        memset(f, 0, sizeof f);//每轮开始前都要制零
        for(int i = 1; i <= n; i++)
            for(int j = price[i][k]; j <= m; j++)//完全背包,正着循环
                f[j] = max(f[j], f[j - price[i][k]] + price[i][k + 1] - price[i][k]);
      
        m += f[m];//加上盈利的钱,进入下一轮买卖
    }
    cout << m;
    return 0;
}

这样就好了(

最后

这篇博客到这里也就结束了,今天主要是介绍了《简单》的动态规划问题(bushi,题目提交地址可以看我的 OJ。(

拜拜~~

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【openWrt】安装后进行定制

修改openWrt管理后台默认端口vim /etc/config/uhttpd修改如下图内容然后重启uhttpd服务即可生效etc/init.d/uhttpd restart修改openWrt软件包源可以在openwrt后台改也可以在/etc/opkg/distfeeds.conf直接改vim /etc/opkg/distfeeds.conf配置如下src/gz handsomemod_core https:…

通讯录的实现

问天下谁与争锋&#xff0c;唯我傲视苍穹 此句赠与在看文章的你 该通讯录使用的语言是C语言&#xff0c;涉及的知识有动态开辟内存&#xff0c;和文件内存管理。 动态开辟内存是用来不断给通讯录增加容量的 文件管理是用来将通讯录的信息存储到文件里。 我会先从简单的写起&am…

公司裁员70%,小组从20个人降到5个人,年底公司耍无赖,全员打绩效C,就为了不发年终奖!...

年终奖写进合同&#xff0c;公司还能耍赖不给吗&#xff1f;一位网友吐槽&#xff1a;坐标小公司&#xff0c;公司裁员70%&#xff0c;自己组从20个人降到5个人。现在年底了&#xff0c;公司耍无赖&#xff0c;全员打绩效C&#xff0c;就为了不发年终奖&#xff01;年终奖都是写…

454. 四数相加 II 383. 赎金信 15. 三数之和 18. 四数之和

454. 四数相加 II 巧用哈希表&#xff0c;哈希表键值对对应的是两数之和&#xff0c;两数之和出现次数。 首先定义 一个unordered_map&#xff0c;key放a和b两数之和&#xff0c;value 放a和b两数之和出现的次数。遍历大A和大B数组&#xff0c;统计两个数组元素之和&#xff0…

Nginx与LUA(4)

您好&#xff0c;我是湘王&#xff0c;这是我的CSDN博客&#xff0c;欢迎您来&#xff0c;欢迎您再来&#xff5e;Nginx既然可以限制流量&#xff0c;那能不能「扩展」流量呢&#xff1f;当然可以&#xff0c;但可能不是你想象的那种「扩展」&#xff0c;更准确地来说是复制&am…

KVM虚拟化基本操作

1&#xff0c;虚拟化的一些介绍 虚拟化软件是可以让一台物理主机建立与执行一至多个虚拟化环境的软件&#xff0c;虚拟化将硬件、操作系统和应用程序一同封装一个可迁移的虚拟机档案文件中。 安装位置分类 目前从Hypervisor(虚拟机管理程序)安装位置分类&#xff0c;虚拟化层…

[linux] 进程相关概念理解

文章目录1. 什么是进程管理本质的解释描述组织结论2.查看进程查看进程方法1创建终端输入命令显示进程一个程序存在多个进程查看进程方法2查看成功查看失败结论3.通过系统调用获取进程标识符1.获取PID值验证PID值是否正确2. 获取父进程PID值验证3. 父进程为什么不变化&#xff1…

Vue3实现九宫格抽奖效果

前言 好久没有写文章了&#xff0c;上一次发文还是年终总结&#xff0c;眨眼间又是一年&#xff0c;每每想多总结却是坚持不来&#xff0c;难顶。  这次分享一个九宫格抽奖小游戏&#xff0c;缘起是最近公司内部做积分抽奖需求&#xff0c;抽出其中抽奖动效做一个总结&#x…

利用steam搬砖信息差项目,投入不到1万,一个月净赚3万+

老实说&#xff1a;我在做之前没有任何经验&#xff0c;但做梦也没想到&#xff0c;刚开始操作收益就远远超出我的预期&#xff01; 这个账号我才运营了一个月左右&#xff0c;就有3万多的销售额。现在我每月的收入都在上万元&#xff0c;而且随着收益越来越多&#xff0c;操作…

亚马逊云科技re:Invent引领云计算未来方向

亚马逊云科技合作伙伴网络大使计划&#xff0c;吸纳拥有多项亚马逊云科技认证和深入亚马逊云科技知识的合作伙伴成员&#xff0c;协助其成为各个领域的技术专家&#xff0c;开发可供公众使用的内容&#xff0c;如技术写作、博客、开源项目&#xff0c;宣传亚马逊云科技及其合作…

JavaWeb基础(四) JSP介绍

JavaWeb基础(四) JSP介绍 1&#xff0c;JSP 概述 JSP&#xff08;全称&#xff1a;Java Server Pages&#xff09;&#xff1a;Java 服务端页面。是一种动态的网页技术&#xff0c;其中既可以定义 HTML、JS、CSS等静态内容&#xff0c;还可以定义 Java代码的动态内容&#xf…

.shp文件的存储结构是怎样的?底层读取shapefile文件

.shp文件的存储结构是怎样的&#xff1f;底层读取shapefile文件基础知识shp的存储结构python 字节流读取Shp文件基础知识 大家都比较熟悉shp文件&#xff0c;它是GIS软件可以读取的矢量文件。但是大家知道它的存储结构吗&#xff1f;这次带着大家聊聊shp文件的存储结构&#x…

Linux diff 命令

Linux diff 命令用于比较文件的差异。diff 以逐行的方式&#xff0c;比较文本文件的异同处。如果指定要比较目录&#xff0c;则 diff 会比较目录中相同文件名的文件&#xff0c;但不会比较其中子目录。语法diff [-abBcdefHilnNpPqrstTuvwy][-<行数>][-C <行数>][-D…

【Java】【系列篇】【Spring源码解析】【三】【体系】【Resource体系】

主要用于加载配置资源等等Resource 前提须知 ClassLoader类的getResource和getResourceAsStream方法是原生JDK中内置的资源加载文件的方式&#xff1b;Spring中资源模型顶级接口不是Resource&#xff0c;而是InputStreamSource接口&#xff1b;Spring为何自己实现一套资源加载…

Nessus 扫描web服务

系列文章 Nessus介绍与安装 Nessus Host Discovery Nessus 高级扫描 Nessus 扫描web服务 1.启动nessus cd nessus sh qd_nessus.sh2.进入nessus网站 https://192.168.3.47:8834/3.点击【New Scan】 4.点击【Web应用程序测试】 5.输入name【web扫描】&#xff0c;描述【web…

Lesson 2. 矩阵运算基础、矩阵求导与最小二乘法

文章目录一、NumPy 矩阵运算基础1. NumPy 中的矩阵表示2. NumPy 中特殊矩阵构造方法3. NumPy 中矩阵基本运算4. NumPy 中矩阵代数运算二、矩阵方程与向量求导方法1. 方程组求解与矩阵方程求解2. 向量求导运算2.1 向量求导基本方法2.2 常见向量求导公式三、最小二乘法的推导过程…

Vue3 函数式组件的开发方式

声明式组件和服务式组件 无论是使用第三方组件库&#xff0c;还是自己封装组件&#xff0c;有一类组件有些与众不同&#xff0c;那就是函数式/服务式组件&#xff0c;比如 Message 消息组件、Notification 通知组件、Loading 加载组件等等。 以 ElementPlus 组件库为例&#…

.net反序列化新手入门--Json.Net

**01 **Json.net简介 Json.net即Newtonsoft.Json&#xff0c;是.Net中开源的Json序列化和反序列化工具&#xff0c;官方地址&#xff1a;http://www.newtonsoft.com/json。 它虽然不是官方库&#xff0c;但凭借其优秀的性能获得了广大开发者的喜爱。 官网给出的性能比较&…

8大预测分析工具比较

什么是预测分析工具&#xff1f; 预测分析工具融合了人工智能和业务报告。这些工具包括用于从整个企业收集数据的复杂管道&#xff0c;添加统计分析和机器学习层以对未来进行预测&#xff0c;并将这些见解提炼成有用的摘要&#xff0c;以便业务用户可以对此采取行动。 预测的…

day17集合

1.Set集合 1.1Set集合概述和特点【应用】 不可以存储重复元素没有索引,不能使用普通for循环遍历 1.2Set集合的使用【应用】 存储字符串并遍历 public class MySet1 {public static void main(String[] args) {//创建集合对象Set<String> set new TreeSet<>()…