动态规划合集——动态规划基本原理

动态规划合集——动态规划基本原理

  • 动态规划原理
    • 1258:【例9.2】数字金字塔 动态规划原理
      • 深度优先搜索
      • 记忆化搜索
      • 动态规划(顺推)
        • 动态规划原理
        • 题解分析
      • 滚动数组优化
      • 动态规划(逆推)

动态规划原理

从数塔问题出发理解动态规划。

1258:【例9.2】数字金字塔 动态规划原理

1258:【例9.2】数字金字塔

深度优先搜索

问题要求的是从最高点按照规则走到最低点的路径的最大的权值和,路径起点终点固定,走法规则明确,可以考虑用搜索来解决。

定义递归函数void Dfs(int x,int y,int Curr),其中(x,y)表示当前已从(1,1)走到(x,y),目前已走路径上的权值和为Curr。同时用另一个变量Ans记录最大值。

x=N时,到达递归出口,如果CurrAns大,则把Ans更新为Curr
否则向下一行两个位置行走,即递归执行
Dfs(x+1,y,Curr+A[x+1][y])Dfs(x+1,y+1,Curr+A[x+1][y+1])

该方法实际上是把所有路径都走了一遍,由于每一条路径都是由N-1步组成,每一步有“左”、“右”两种选择,因此路径总数为 2 N − 1 2^{N-1} 2N1
所以该方法的时间复杂度为 O ( 2 N − 1 ) O(2^{N-1}) O(2N1),铁定超时。

#include <iostream>
using namespace std;
int A[1005][1005], F[1005][1005], N, Ans;
void Dfs(int x, int y, int Curr) {if (x == N) {if (Curr > Ans)Ans = Curr;return;}//深度优先搜索是枚举所有情况,这里只有两个,而且用形参表示状态,回溯无难度Dfs(x + 1, y, Curr + A[x + 1][y]);Dfs(x + 1, y + 1, Curr + A[x + 1][y + 1]);
}
int main() {cin >> N;for (int i = 1; i <= N; i++)for (int j = 1; j <= i; j++)cin >> A[i][j];Ans = 0;Dfs(1, 1, A[1][1]);cout << Ans << endl;return 0;
}

记忆化搜索

为了避免重复搜索,我们在dfs的基础上开设全局数组F[x][y]记录从(x,y)出发到终点路径的最大权值和,一开始全部初始化为-1表示未被计算过。

在计算Dfs(x,y)时,首先查询F[x][y],如果F[x][y]不等于-1,说明Dfs(x,y)之前已经被计算过,直接返回 F[x][y]即可,否则计算出Dfs(x,y)的值并存储在F[x][y]中。

由于F[x][y]对于每个合法的(x,y)都只计算过一次,
而且计算是在 O ( 1 ) O(1) O(1)内完成的,因此时间复杂度为 O ( N 2 ) O(N^2) O(N2)。可以通过本题。

记忆化搜索的本质已经是动态规划。因为记忆化搜索也是记录走过的分支,相当于已经解决了子问题,再遍历这条路径时直接由子问题解决当前问题即可。

#include <iostream>
#include <algorithm>
using namespace std;
int A[505][505],
F[505][505],N;
int Dfs(int x,int y) {if (F[x][y]== -1) {if (x==N)F[x][y]=A[x][y];elseF[x][y]=A[x][y]+max(Dfs(x+1,y),Dfs(x+1,y+1));}return F[x][y];
}
int main() {cin >> N;for(int i = 1;i <= N;i ++)for(int j = 1;j <= i;j ++)cin >> A[i][j];for(int i = 1;i <= N;i ++)for(int j = 1;j <= i;j ++)F[i][j] = -1;Dfs(1,1);cout << F[1][1] << endl;return 0;
}

动态规划(顺推)

动态规划原理

动态规划(Dynamic Programming,简称dp)是1951年美国数学家R.Bellman等人提出。具体详见动态规划_百度百科。

简单总结就是

  1. 一个大问题拆分成若干子问题,每个子问题的结果都是一个状态

  2. 由一个子问题解决另一个子问题选择的方法称为决策,在现实解决问题时我们都希望找到最优解,也就是最优决策

  3. 同一个大问题拆分成的子问题之间往往都有一样的规律。或者说,同一个大问题产生的任意两个不同状态之间有相同的联系。这个联系一般用数学公式表达,这个公式的专业术语叫状态转移方程

  4. 动态规划满足两个条件:

    • 最优化原理:每一步决策都是最好的解决方案,都是从很多方案中选择最好的方案。
    • 无后效性:每个状态除了第一个,都只由前阶段的状态和决策决定,和后续无关。
题解分析

1、状态定义:
题目要求从(1,1)出发到最底层路径最大权值和,路径中是各个点串联而成,路径起点固定,终点和中间点相对不固定。因此定义F[x][y]表示从(1,1)出发到达(x,y)的路径最大权值和。
最终答案Ans=max{F[N][1],F[N][2],...,F[N][N]}

2、状态转移方程&边界条件(或初始化方式):
不去考虑(1,1)(x,y)的每一步是如何走的,只考虑最后一步是如何走,根据最后一步是向
左还是向右分成以下两种情况:
向左:最后一步是从(x-1,y)走到(x,y), 此类路径被分割成两部分,

第一部分是从(1,1)走到(x-1,y),第二部分是从(x-1,y)走到(x,y)

第一部分的最大权值和,此部分问题的性质与F[x][y]的定义一样,就是F[x-1][y]

第二部分就是A[x][y],两部分相加即得到此类路径的最大权值和为F[x-1,y]+A[x,y]

向右: 最后一步是从(x-1,y-1)走到(x,y),此类路径被分割成两部分,

第一部分是从(1,1)走到(x-1,y-1),第二部分是从(x-1,y-1)走到(x,y),分析方法如上。

此类路径的最大权值和为
F[x-1,y-1]+A[x,y]

F[x][y]的计算需要求出上面两种情况的最大值。

综上,得到状态转移方程如下:
F[x][y]=max{F[x-1,y-1],F[x-1][y]}+A[x,y]

与递归关系式还需要递归终止条件一样,这里我们需要对边界进行处理以防止无休止地进行下去。观察发现计算F[x][y]时需要用到F[x-1][y-1]F[x-1][y],是上一行的元素,随着递归的深入,
最终都要用到第一行的元素F[1][1], F[1][1]的计算不能再使用状态转移方程来求,而是应该直接赋予一个特值A[1][1]。这就是边界条件

综上得:
状态转移方程F[x][y]=max{F[x-1][y-1],F[x-1][y]}+A[x,y]
边界条件F[1][1]=A[1][1]

分析该动态规划的正确性,分析该解法是否满足使用动态规划的两个前提。

最优化原理:这个在分析状态转移方程时已经分析得比较透彻,明显是符合最优化原理的。

无后效性:状态转移方程中,我们只关心F[x-1][y-1]F[x-1][y]的值,计算F[x-1][y-1]时可能有多种不同的决策对应着最优值,选哪种决策对计算F[x][y]的决策没有影响,
F[x-1][y]也是一样。这就是无后效性。

在以后的题目中可能不会提及,但处处都充斥着这两个前提的分析。

3、填表(或程序实现):
由于状态转移方程就是递归关系式,边界条件就是递归终止条件,所以可以用递归来完成,递归存在重复调用,利用记忆化可以解决重复调用的问题,这就是方法二的记忆化搜索

记忆化实现比较简单,而且不会计算无用状态,但递归也会受到栈的大小递推加回归执行方式的约束,另外记忆化实现调用状态的顺序是按照实际需求而展开,没有大局规划,不利于进一步优化

所以dp更常用的还是迭代法。状态用二维数组表示,也就是说可以通过循环的方式求出每个状态(通俗点称呼就是填表)。

计算F[x][y]用到状态F[x-1][y-1]F[x-1][y],这些元素在F[x][y]的上一行,也就是说要计算第x行的状态的值,必须要先把第x-1行元素的值计算出来,因此我们可以先把第一行元素F[1][1]赋为 A[1][1],再从第二行开始按照行从左到右递增从上到下递增的顺序通过循环计算出每一行的有效状态即可。时间复杂度为 O ( N 2 ) O(N^2) O(N2)

参考程序:

#include <iostream>
#include <algorithm>
using namespace std;
int A[1005][1005], F[1005][1005], N;
int main() {cin >> N;for (int i = 1; i <= N; i++)for (int j = 1; j <= i; j++)cin >> A[i][j];F[1][1] = A[1][1];for (int i = 2; i <= N; i++)for (int j = 1; j <= i; j++)F[i][j] = max(F[i - 1][j - 1], F[i - 1][j]) + A[i][j];int ans = 0;for (int i = 1; i <= N; i++)ans = max(ans, F[N][i]);cout << ans << endl;return 0;
}

滚动数组优化

根据之前得到的状态转移方程F[x][y]=max{F[x-1][y-1],F[x-1][y]}+A[x,y]
边界条件F[1][1]=A[1][1]

发现在二维数组表示的转移方程中,每个状态都只和上一个状态有关。而且每个状态都只和前1个状态有关,和前2个以及之前的所有状态都无关。

所以可以将表示状态的dp表用一维数组表示。状态转移方程变为

f[x]=max(f[x],f[x-1])+A[x,y]

这种不改变空间复杂度,但可以极大程度地优化空间复杂度的状态表示称作滚动数组优化。

在填表的时候,需要将循环从右往左枚举,才能保证每个状态都是正确的。

请添加图片描述

因为都是正数,所以不必考虑边界为0时造成的影响。如果存在负数,则可能需要对边界情况做判断,或取无穷小。

滚动数组优化参考程序:

#include<bits/stdc++.h>
using namespace std;int main() {int n;cin >> n;vector<vector<int> >A(1, vector<int>(2,0));vector<int>f(n+1,0);for (int i = 1; i <= n; i++) {A.push_back(vector<int>(i + 2, 0));for (int j = 1; j <= i; j++)cin >> A[i][j];}int ans = A[1][1];for (int i = 1; i <= n; i++) {for (int j = i; j >= 1; j--) {//逆向枚举f[j] = max(f[j], f[j - 1]) + A[i][j];ans = max(ans, f[j]);}}cout << ans;return 0;
}

整体来说滚动数组优化的步骤:

  1. 先用二维或多维解决问题。
  2. 若状态转移方程的状态只和上一层或上几层格子有关,则可以考虑优化。否则不能优化。
  3. 若判断出可以优化,则将原来的二维和多维状态减少适当的维度,并看情况修改循环的枚举顺序。

当然不是所有的题都能做空间优化。

动态规划(逆推)

这里思路和之前是一样的,只是状态的设定不同。

以这个样例为例:

5
13
11 8
12 7 26
6 14 15 8
12 7 13 24 11

自底向上计算:(给出递推式和终止条件)

从底层开始,本身数即为最大数;

倒数第二层的计算,取决于底层的数据,每次都取最大:
12 + 6 = 18 , 13 + 14 = 27 , 24 + 15 = 39 , 24 + 8 = 32 12+6=18,13+14=27,24+15=39,24+8=32 12+6=1813+14=2724+15=3924+8=32
倒数第三层的计算,取决于底二层计算的数据,每次都取最大:
27 + 12 = 39 , 39 + 7 = 46 , 39 + 26 = 65 27+12=39,39+7=46,39+26=65 27+12=3939+7=4639+26=65
倒数第四层的计算,取决于底三层计算的数据,每次都取最大:
46 + 11 = 57 , 65 + 8 = 73 46+11=57,65+8=73 46+11=5765+8=73
最后的路径:
5—>13—>8->26—>15—>24

这是手搓简单情况,复杂情况人的效率远远不及计算机,需要交给计算机来做。

数据结构及算法设计

图形转化:直角三角形,便于搜索:向下、向右。

用三维数组表示数塔:

a[x][y][1]表示行、列及结点本身数据,
a[x][y][2]能够取得最大值,
a[x][y][3]表示前进的方向,0表示向下,1表示向右。

算法实现

数组初始化,输入每个结点值及初始的最大路径、前进方向为0;从倒数第二层开始向上一层求最大路径,共循环N-1次;从顶向下,输出路径:究竟向下还是向右取决于列的值,若列的值比原先多则向右,否则向下。

#include<iostream>
using namespace std;
int main()
{int n, x, y;int a[1001][1001][4]={0};cin >> n;for (x = 1; x <= n; x++)//输入数塔的初始值for (y = 1; y <= x; y++) {cin >> a[x][y][1];//1表示值a[x][y][2] = a[x][y][1];//2表示当前状态,未经计算时默认是原来的数a[x][y][3] = 0;//3表示路径走向,默认向下}for (x = n - 1; x >= 1; x--)//从倒数第二行开始推 for (y = 1; y <= x; y++)if (a[x + 1][y][2] > a[x + 1][y + 1][2]) {//选择最大路径值a[x][y][2] = a[x][y][2] + a[x + 1][y][2];a[x][y][3] = 0;}else {a[x][y][2] = a[x][y][2] + a[x + 1][y + 1][2];a[x][y][3] = 1;}cout << a[1][1][2] << endl;//输出数塔最大值枚举路径,这题可忽略//y = 1;//for (x = 1; x <= n - 1; x++){//输出数塔最大值的路径//	cout << a[x][y][1] << "->";//	y = y + a[x][y][3];//下一行的列数//}//cout << a[n][y][1] << endl;return 0;
}

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

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

相关文章

如何让节卡机器人精准对点?

如何让节卡机器人精准对点&#xff1f; JAKA Zu 软件主界面主要由功能栏、开关栏、菜单栏构成。 菜单栏&#xff1a;控制柜管理&#xff0c;机器人管理与软件管理组成。主要功能为对控制柜关机、APP 设置、机器人本体设 置、控制柜设置、连接机器人和机器人显示等功能。 开关…

自动化测试工具-Playwright介绍和快速实例

Playwright 是什么 Playwright 是由 Microsoft 开发的开源自动化测试工具,专为现代 Web 应用设计。它支持 Chromium、Firefox 和 WebKit 内核的浏览器,能够跨平台(Windows、macOS、Linux)运行,提供强大的浏览器自动化能力,适用于测试、爬虫和监控等场景。 Playwright的…

软考程序员考试知识点汇总

软考程序员考试&#xff08;初级资格&#xff09;主要考察计算机基础理论、编程能力及软件开发相关知识。以下是核心知识点总结及备考建议&#xff1a; 一、计算机基础 数制与编码 二进制、八进制、十进制、十六进制转换原码、反码、补码表示&#xff08;整数与浮点数&#xf…

实时视频分析的破局之道:蓝耘 MaaS 如何与海螺 AI 视频实现高效协同

一、蓝耘 MaaS 平台&#xff1a;AI 模型全生命周期管理的智能引擎 蓝耘 MaaS&#xff08;Model-as-a-Service&#xff09;平台是由蓝耘科技推出的 AI 模型全生命周期管理平台&#xff0c;专注于为企业和开发者提供从模型训练、推理到部署的一站式解决方案。依托云原生架构、高…

设计模式(行为型)-策略模式

目录 定义 类图 角色 角色详解 Strategy&#xff08;抽象策略类&#xff09;​ Context&#xff08;环境类 / 上下文类&#xff09;​ ConcreteStrategy&#xff08;具体策略类&#xff09;​ 优缺点 优点​ 缺点​ 使用场景 类行为差异场景​ 动态算法选…

【算法day14】三数之和

三数之和 https://leetcode.cn/problems/3sum/description/ 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请你返回所有和为 0 且不重复的三元组。…

优化器/模型参数/超参数

参数&#xff08;Parameters&#xff09; vs. 超参数&#xff08;Hyperparameters&#xff09; 1.1 参数&#xff08;Parameters&#xff09; 定义&#xff1a;模型中需要学习的变量&#xff0c;例如神经网络中的权重&#xff08;Weight&#xff09;和偏置&#xff08;Bias&a…

10、STL中的unordered_map使用方法

一、了解 1、unordered_map(哈希) unordered_map是借用哈希表实现的关联容器。 访问键值对O&#xff08;1&#xff09;&#xff0c;最坏情况O&#xff08;n&#xff09;&#xff0c;例如哈希冲突严重时。【n是一个哈希桶的元素数量】 unordered_map特性 键值对存储&#xff…

C++ 头文件说明

如果一个程序足够大&#xff0c;代码功能很多&#xff0c;可以想象&#xff0c;不可能把代码写在一个cpp文件里。我们需要模块化&#xff0c;这样的好处很多&#xff0c;方便分工合作&#xff0c;可读性提高&#xff0c;调用也方便。 这个要怎么做呢&#xff1f; 很简单直接当…

Lambda 表达式的语法:

在 Java 中&#xff0c;Lambda 表达式&#xff08;也称为匿名方法&#xff09;是一种简洁的表示方法接口&#xff08;Functional Interface&#xff09;实现的方式。它是 Java 8 引入的特性&#xff0c;目的是提高代码的简洁性和可读性。 Lambda 表达式的语法&#xff1a; La…

C#零基础入门篇(18. 文件操作指南)

## 一、文件操作基础 在C#中&#xff0c;文件操作主要通过System.IO命名空间中的类来实现&#xff0c;例如File、FileStream、FileInfo等。 ## 二、常用文件操作方法 ### &#xff08;一&#xff09;文件读取 1. **使用File.ReadAllText方法读取文件内容为字符串** …

每日一题--内存池

内存池&#xff08;Memory Pool&#xff09;是一种高效的内存管理技术&#xff0c;通过预先分配并自主管理内存块&#xff0c;减少频繁申请/释放内存的系统开销&#xff0c;提升程序性能。它是高性能编程&#xff08;如游戏引擎、数据库、网络服务器&#xff09;中的核心优化手…

【Linux系统】Linux进程终止的N种方式

Linux系列 文章目录 Linux系列前言一、进程终止的概念二、进程终止的场景三、进程终止的实现3.1 程序退出码3.2 运行完毕结果正常3.3 运行完毕结果异常3.4 程序异常退出 总结 前言 进程终止是操作系统中&#xff0c;进程的一个重要阶段&#xff0c;他标志着进程生命周期的结束…

正则表达式引擎深入探讨

正则表达式引擎&#xff08;Regular Expression Engine&#xff09;是正则表达式得以“活起来”的核心。它是一个精密的软件组件&#xff0c;负责接收正则表达式和输入文本&#xff0c;解析模式并执行匹配或替换操作&#xff0c;最终输出结果——可能是简单的“是否匹配”&…

java面试题,什么是动态代理?、动态代理和静态代理有什么区别?说一下反射机制?JDK Proxy 和 CGLib 有什么区别?动态代理的底层

什么是动态代理&#xff1f; 动态代理是在程序运行期&#xff0c;动态的创建目标对象的代理对象&#xff0c;并对目标对象中的方法进行功能性增强的一种技术。 在生成代理对象的过程中&#xff0c;目标对象不变&#xff0c;代理对象中的方法是目标对象方法的增强方法。可以理解…

【工具类】Java的 LocalDate 获取本月第一天和最后一天

博主介绍&#xff1a;✌全网粉丝22W&#xff0c;CSDN博客专家、Java领域优质创作者&#xff0c;掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围&#xff1a;SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…

嵌入式开发之STM32学习笔记day06

基于STM32F103C8T6的开发实践——从入门到精通01 1. 引言 STM32系列微控制器是STMicroelectronics推出的一款高性能、低功耗的32位微控制器&#xff0c;广泛应用于嵌入式系统中。STM32F103C8T6是其中非常受欢迎的一款&#xff0c;凭借其强大的性能、丰富的外设接口和低廉的价格…

学习使用 Git 和 GitHub 开发项目的教程推荐

Git 和 GitHub 是现代软件开发中不可或缺的工具&#xff0c;无论你是个人开发者还是团队成员&#xff0c;掌握它们都能极大提升效率。本文精选了一系列优质教程资源&#xff0c;涵盖从基本 Git 命令到进阶多人协作的内容。这些教程既有文字形式&#xff0c;也有视频或交互式资源…

golang中的接口

1.简介 在go中的接口是以一种类型,一种抽象的类型。接口(interface)是一组函数method的集合,go中的接口不能包含任何变量。在go中接口中的所有方法都没有方法体,接口定义了一个对象的行为规范,只定义规范不实现。接口体现了程序的多态和高内聚低耦合的思想。go中的接口也是…

AI 浪潮下,职场的变与不变

如今&#xff0c;AI 如迅猛飓风&#xff0c;极速席卷职场&#xff0c;彻底搅乱了原有的秩序。你是否留意到&#xff0c;身边的工作方式正悄然生变&#xff1f;今天&#xff0c;【探星 AI 研习社】就为大家深入剖析&#xff0c;AI 如何改写职场剧本。无论你是大学生还是职场资深…