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

C++动态规划经典案例解析之合并石子

1. 前言

区间类型问题,指求一个数列中某一段区间的值,包括求和、最值等简单或复杂问题。此类问题也适用于动态规划思想。

前缀和就是极简单的区间问题。如有如下数组:

int nums[]={3,1,7,9,12,78,32,5,10,11,21,32,45,22}

现给定区间信息[3,6],求区间内所有数字相加结果。即求如下图位置数字之和。

Tips: 区间至少包括 2 个属性,起始端和结束端,求和范围包含左端和右端数字。

1.png

直接的解法:

  • 累加数组中 0~6区间的值s1
  • 累加数组中0~2区间的值s2
  • s1中的值减去s2中的值。得到最终结果。

如果对任意区间的求解要求较频繁,会存在大量的重复计算。如分别求区间[2,5][1,5]之和时,分析可知区间[1,5]结果等于区间[2,5]的结果加上nums[1]的值,或者说区间[2,5]的值等于[1,5]的值减nums[1]。简而言之,只需要求出一个如上两个区间中一个区间的值,另一个区间的值就可得到。

2.png

为了减少重复计算,可使用区间缓存理念记录0~至任意位置的和。

3.png

如上的问题便是简单的区间类型问题,解决此类问题的方案称为简单区间类型动态规划。dp数组也可称为前缀和数组。

编码实现:

#include <iostream>
using namespace std;
int main() {int nums[]= {3,1,7,9,12,78,32,5,10,11,21,32,45,22};int dp[100];int size=sizeof(nums)/sizeof(int);for(int i=0; i<size; i++) {if(i==0){//base case dp[i]=nums[i];}else{dp[i]=dp[i-1]+nums[i];}}//输出dp信息for(int i=0; i<size; i++) {cout<<dp[i]<<"\t";}return 0;
}

有了前缀和数组,计算任意区间数字和的公式为:

//[l,r]:l表示左端位置,r表示右端位置
dp[r]-dp[l-1];

如下代码实现,输入任意区间信息,输出区间和信息。

#include <iostream>
using namespace std;
int main() {int nums[]= {3,1,7,9,12,78,32,5,10,11,21,32,45,22};int dp[100];int size=sizeof(nums)/sizeof(int);for(int i=0; i<size; i++) {if(i==0) {//base casedp[i]=nums[i];} else {dp[i]=dp[i-1]+nums[i];}}//输出dp信息for(int i=0; i<size; i++) {cout<<dp[i]<<"\t";}cout<<endl;int l,r,sum;while(1) {cin>>l>>r;if(l==-1)break;sum=dp[r]-dp[l-1];cout<<sum<<endl;}return 0;
}

前缀和是区间动态规划的极简单应用,下文继续讲解几道典型的区间类型问题。

2. 典型案例

2.1 石子合并

问题描述:

设有N(N<=300)堆石子排成一排,其编号为1,2,3...N,每堆石子有一定的质量m[i] (m[i]<=1000)。现在要将这N堆石子合并成为一堆,每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后与这两堆石子相邻的石子将和新堆相邻。合并时由于选择的顺序不同,合并的总代价也不相同。试找出一种合理的方法,使总的代价最小,并输出最小代价。

此问题为什么也是区间类型问题?

先看几个样例。如有编号为 1,2,3 的 3 堆石子,质量分别为 1,2,3。则合并方案有如下 2 种:

  • 合并编号为1、2的石子,合并代价为3,再合并新堆和第3堆石子,代价为6。总代价为9
  • 合并编号为2、3的石子,合并代价为5,再合并新堆和第1堆石子,代价为6。总代价为11

通过上述合并过程,可得到如下有用的结论:

  • 任意相邻两堆石子合并的结果是以这两堆石子的编号作为左、右边界的区间和。如合并编号1,2的石子,代价为区间[1,2]的和 。合并编号为2、3的石子,结果为区间[2,3]的和。

4.png

  • 无论采用何种合并方案,最后一次合并都是相当于求整个数列的和。

    如样例所示,两种合并方案的代价分别为3,5,取最小值3再加上所有石子的质量和6,即为最后答案9

5.png

  • 对于n堆的石子,可以随意在中间画出一条分割线,把n堆石子抽象成左、右2 堆石子(两个区间),根据上述分析,可知最后一次的合并值为这 2堆石子的质量总和。

    但是,左堆不是真正意义上只有一堆石子,是由许多石子堆组成的一个逻辑整体,有其内部的合并方案,且不止一种,站在宏观的角度,不用关心其内部如何变化,只需关心多种合并方案的最小值是多少。同理,也只需关心右堆最终返回的最佳值。

    所以,求解问题可以抽象成:

    最终合并最小值=所有石子堆的总质量值+左堆最小合并值+右堆最小合并值
    

6.png

如果原始问题是一个根问题,则求解左堆或右堆的最佳合并值就是一个子问题,所以,合并石子这道题本质是符合递归特点的。

既然符合递归特点,现在就要考虑如何划分子问题。

绘制如下图递归树,根问题为原始问题,区间划分可以从第一堆石子开始,然后再移动分割线,最后再在多个子问题返回值中取最小值。

Tips: 如果只有一堆石子,则代价为 0

7.png

编码实现:

  • 初如化变量。
#include <iostream>
#include <cmath>
using namespace std;
//石子质量 
int sz[100]={0};
//石子堆数量
int n; 
//前缀和
int s[100]={0}; 
  • 初始化石子信息和前缀和。
/*
*  初始化 
*/
void init(){cin>>n;for(int i=1;i<=n;i++){cin>>sz[i];}//动态规划计算前缀和for(int i=1;i<=n;i++){s[i]=s[i-1]+sz[i];} 
}
  • 递归实现,子问题是一个区间问题,由左、右分界线确定。
int  getSz(int l,int r) {//只有一堆石子,返回 0if(l==r)return 0;int res=1<<30;//得到区间的和,最后一次合并值int sum=s[r]-s[l-1];//计算可分方案,且返回所有分割方案中的最小值for(int k=l; k<r; k++) {res=min(res,  getSz(l,k)  + getSz(k+1,r) );}//返回最后一次合并的值加上左、右区间的合并值return sum+res;
}
  • 测试。
int main() {init();int res= getSz(1,n);cout<<res;return 0;
}

是否存在重叠子问题?

如下图所示,当石子堆更多时,重叠子问题更多。

8.png

使用记忆搜索解决重叠子问题。

//记忆数组
int dp[100][100]={0}; 
int  getSz(int l,int r) {//只有一堆石子,返回 0if(l==r)return 0;if(dp[l][r]!=0)return dp[l][r];int res=1<<30;//得到区间的和,最后一次合并值int sum=s[r]-s[l-1];//计算可分方案,且返回所有分割方案中的最小值for(int k=l; k<r; k++) {res=min(res,  getSz(l,k)  + getSz(k+1,r) );}return dp[l][r]=sum+res;
}

递归是由上向下逐步向子问题求助,类似问题也可以采用由下向上的动态规划方案实现。基本思路,每一次合并过程,先两两合并,再三三合并,…最后N堆合并。

/*
*动态规划
*/
int dpSz() {int ans=0;//初始化dp 数组for (int i = 1; i <= n; i++) {for(int j=1; j<=n; j++) {dp[i][j]=1<<30;}//一堆石子的值为 0dp[i][i] = 0;}//从长度为 1 的区间开始扫描,逐步增加区间的长度for (int len = 1; len < n; len++)//左边界for (int i = 1; i < n; i++) {//右边界int j = i + len;//左右之间的所有子区间for (int k = i; k < j; k++) {dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + s[j] - s[i - 1]);}ans=max(ans,dp[i][j]);}return ans;
}

测试:

int main() {init();int res=dpSz();cout<<"动态规划方案:"<<res<<endl;printf("%d\n", dp[1][n]);return 0;
}

2.2 石子合并 II

问题描述:

有 n 堆石子围成一个圈,第 i 堆石子有 a[i] 颗,每次我们可以选择相邻的两堆石子合并,代价是两堆石子数目的和,现在我们要一直合并这些石子,使得最后只剩下一堆石子,问总代价最少是多少?

因为首尾可合并,相比较上述问题,差异在于增加合并的方案。

那么,到底增加了那些合并?

假设石子有 3 堆,每堆的质量分别为 1 2 3

如果考虑环形问题,则任何数字都可以为头、为尾,则会出现如下几种数列。

  • 1 2 3

  • 2 3 1

  • 3 1 2

9.png

可以理解,数列变成如下 形式,即将环形变成线性。

10.png

动态规划实现:

#include <bits/stdc++.h>
using namespace std;int n, a[501], f[501][501], s[501];int main() {scanf("%d", &n);for (int i = 1; i <= n; i++) {scanf("%d", &a[i]);a[n + i] = a[i];}    for (int i = 1; i <= 2 * n; i++)s[i] = s[i - 1] + a[i];memset(f, 127, sizeof(f));for (int i = 1; i <= 2 * n; i++)f[i][i] = 0;for (int l = 1; l < 2 *n; l++)for (int i = 1; i <= 2 * n - l; i++) {int j = i + l;for (int k = i; k < j; k++)f[i][j] = min(f[i][j], f[i][k] + f[k + 1][j] + s[j] - s[i - 1]);}int ans = 1 << 30;for (int i = 1; i <= n; i++)ans = min(ans, f[i][i + n - 1]);printf("%d\n", ans);
}

3. 总结

沉淀过程是一种修行。

相关文章:

C++动态规划经典案例解析之合并石子

1. 前言 区间类型问题&#xff0c;指求一个数列中某一段区间的值&#xff0c;包括求和、最值等简单或复杂问题。此类问题也适用于动态规划思想。 如前缀和就是极简单的区间问题。如有如下数组&#xff1a; int nums[]{3,1,7,9,12,78,32,5,10,11,21,32,45,22}现给定区间信息[…...

go MongoDB

安装 go get go.mongodb.org/mongo-driver/mongo package mongodbexampleimport ("context""fmt""ginapi/structs""time""go.mongodb.org/mongo-driver/bson""go.mongodb.org/mongo-driver/bson/primitive""…...

算法与数据结构(八)--优先队列

普通的队列是一种先进先出的数据结构&#xff0c;元素在队列尾追加&#xff0c;而从队列头删除&#xff0c;在某些情况下&#xff0c;我们可能需要找出队列中的最大值或者最小值。 例如使用一个队列保存计算机的任务&#xff0c;一般情况下计算机的任务都是有优先级的&#xff…...

React 全栈体系(三)

第二章 React面向组件编程 四、组件三大核心属性3: refs与事件处理 1. 效果 需求: 自定义组件, 功能说明如下: 点击按钮, 提示第一个输入框中的值当第2个输入框失去焦点时, 提示这个输入框中的值 2. 理解 组件内的标签可以定义ref属性来标识自己 3. 编码 3.1 字符串形式…...

腾讯云下一代CDN -- EdgeOne加速MinIO对象存储

省流 使用MinIO作为EdgeOne的源站。 背景介绍 项目中需要一个兼容S3协议的对象存储服务&#xff0c;腾讯云的COS虽然也兼容S3协议&#xff0c;但是也只是支持简单的上传下载&#xff0c;对于上传的时候同时打标签这种需求&#xff0c;就不兼容S3了。所以决定自建一个对象存储…...

GitLab-CI 指南

GitLab CI 指南 前置工作 部署GitLab 部署GitLab-Runner 注册Runner到GitLab docker exec -it gitlab-runner bash # 进入容器 gitlab-runner register #调用register命令开始注册 # 在Gitlab Setting中找到Runners,如下图所示Enter the GitLab instance URL (for example, …...

MyBatis的核心技术掌握,简单易懂(上)

目录 一.MyBatis中的动态SQL 二.MyBatis中的模糊查询 1. # 符号 2. $ 符号 ---问题 ---所以大家知道 # 和 $ 在MyBatis中的模糊查询中的区别了嘛&#xff1f;&#xff1f; 三.MyBatis 中的结果映射 1. resultType&#xff1a; 2. resultMap&#xff1a; ---问题 ---…...

Redisson自定义序列化

Redisson自定义序列化_redisson 序列化_yzh_1346983557的博客-CSDN博客 redis存取的数据一定是可序列化的&#xff0c;而可序列化方式可以自定义。如果不同客户端设置的可序列化方式不一样&#xff0c;会导致读取不一致的问题。常见的序列化方式有几下几种...

MongoDB Long 类型 shell 查询

场景 1、某数据ID为Long类型&#xff0c;JAVA 定义实体类 Id Long id 2、查询数据库&#xff0c;此数据存在 3、使用 shell 查询&#xff0c;查不到数据 4、JAVA代码查询Query.query 不受任何影响 分析 尝试解决&#xff08;一&#xff09; long 在 mongo中为 int64 类型…...

回归预测 | MATLAB实现GA-APSO-IBP改进遗传-粒子群算法优化双层BP神经网络多输入单输出回归预测

回归预测 | MATLAB实现GA-APSO-IBP改进遗传-粒子群算法优化双层BP神经网络多输入单输出回归预测 目录 回归预测 | MATLAB实现GA-APSO-IBP改进遗传-粒子群算法优化双层BP神经网络多输入单输出回归预测效果一览基本介绍模型描述程序设计参考资料 效果一览 基本介绍 MATLAB实现GA-…...

Spring cache整合Redis使用介绍

&#x1f353; 简介&#xff1a;java系列技术分享(&#x1f449;持续更新中…&#x1f525;) &#x1f353; 初衷:一起学习、一起进步、坚持不懈 &#x1f353; 如果文章内容有误与您的想法不一致,欢迎大家在评论区指正&#x1f64f; &#x1f353; 希望这篇文章对你有所帮助,欢…...

Metasploit提权

一、bypassuac 用户账户控制&#xff08;User Account Control&#xff0c;简写作UAC)是微软公司在其Windows Vista及更高版本操作系统中采用的一种控制机制。其原理是通知用户是否对应用程序使用硬盘驱动器和系统文件授权&#xff0c;以达到帮助阻止恶意程序&#xff08;有时也…...

TypeScript三种特殊类型

1.any类型 说明&#xff1a;any类型代表着可以赋值任意类型 let nickname:any"王二"nickname15nicknametruenicknameundefinednicknamenullnickname{}2.unknown类型 说明&#xff1a;类似any类型&#xff1b;只是不能赋值到其它类型上&#xff1b;除了any和known。…...

如何使用CSS实现一个响应式轮播图?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 使用CSS实现响应式轮播图的示例⭐ HTML 结构⭐ CSS 样式 (styles.css)⭐ JavaScript 代码 (script.js)⭐ 实现说明⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带…...

数据生成 | MATLAB实现MCMC马尔科夫蒙特卡洛模拟的数据生成

数据生成 | MATLAB实现MCMC马尔科夫蒙特卡洛模拟的数据生成 目录 数据生成 | MATLAB实现MCMC马尔科夫蒙特卡洛模拟的数据生成生成效果基本描述模型描述程序设计参考资料 生成效果 基本描述 1.MATLAB实现MCMC马尔科夫蒙特卡洛模拟的数据生成&#xff1b; 2.马尔科夫链蒙特卡洛方…...

【从零开始的rust web开发之路 二】axum中间件和共享状态使用

系列文章目录 第一章 axum学习使用 第二章 axum中间件使用 文章目录 系列文章目录前言一、中间件是什么二、中间件使用常用中间件使用中间件使用TraceLayer中间件实现请求日志打印自定义中间件 共享状态 前言 上篇文件讲了路由和参数相应相关的。axum还有个关键的地方是中间件…...

Vue操作时间

一、获取现在时间 const currentTime () > {let date new Date();let year date.getFullYear(); //月份从0~11&#xff0c;所以加一let month date.getMonth();let dateArr [date.getMonth() 1,date.getDate(),date.getHours(),date.getMinutes(),date.getSeconds(),…...

数据库——Redis 常见数据结构以及使用场景分析

文章目录 1. string2. list3. hash4. set5. sorted set 你可以自己本机安装 redis 或者通过 redis 官网提供的在线 redis 环境。 1. string 介绍 &#xff1a;string 数据结构是简单的 key-value 类型。虽然 Redis 是用 C 语言写的&#xff0c;但是 Redis 并没有使用 C 的字符串…...

数学建模-规划工具箱yalmip

官网下载 实例 %% yalmip 求解 yalmip clc;clear;close all; %% %sdpvar实型变量 intvar 整形变量 binvar 0-1型变量 psdpvar(3,1); %定义变量 %目标函数 要把求最大值转化为最小值 Objective-p(1)^2p(2)^2-p(2)*p(3);%约束条件 Constraints[0<p<1,(p(1)^2p…...

[SQL挖掘机] - 窗口函数 - 计算移动平均

介绍: 在窗口函数使用时&#xff0c;计算的是累积到当前行的所有的数据的相关操作。 实际上&#xff0c;还可以指定更加详细的汇总范围。该汇总范围称为 框架 (frame)。 其实这里也可以理解成一个窗口, 这个窗口是我们可以进行设置的. 之前我们介绍的窗口函数是根据partition…...

stm32G473的flash模式是单bank还是双bank?

今天突然有人stm32G473的flash模式是单bank还是双bank&#xff1f;由于时间太久&#xff0c;我真忘记了。搜搜发现&#xff0c;还真有人和我一样。见下面的链接&#xff1a;https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...

oracle与MySQL数据库之间数据同步的技术要点

Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异&#xff0c;它们的数据同步要求既要保持数据的准确性和一致性&#xff0c;又要处理好性能问题。以下是一些主要的技术要点&#xff1a; 数据结构差异 数据类型差异&#xff…...

Java多线程实现之Callable接口深度解析

Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...

《通信之道——从微积分到 5G》读书总结

第1章 绪 论 1.1 这是一本什么样的书 通信技术&#xff0c;说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号&#xff08;调制&#xff09; 把信息从信号中抽取出来&am…...

第25节 Node.js 断言测试

Node.js的assert模块主要用于编写程序的单元测试时使用&#xff0c;通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试&#xff0c;通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...

Nginx server_name 配置说明

Nginx 是一个高性能的反向代理和负载均衡服务器&#xff0c;其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机&#xff08;Virtual Host&#xff09;。 1. 简介 Nginx 使用 server_name 指令来确定…...

OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别

OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...

Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理

引言 Bitmap&#xff08;位图&#xff09;是Android应用内存占用的“头号杀手”。一张1080P&#xff08;1920x1080&#xff09;的图片以ARGB_8888格式加载时&#xff0c;内存占用高达8MB&#xff08;192010804字节&#xff09;。据统计&#xff0c;超过60%的应用OOM崩溃与Bitm…...

Linux离线(zip方式)安装docker

目录 基础信息操作系统信息docker信息 安装实例安装步骤示例 遇到的问题问题1&#xff1a;修改默认工作路径启动失败问题2 找不到对应组 基础信息 操作系统信息 OS版本&#xff1a;CentOS 7 64位 内核版本&#xff1a;3.10.0 相关命令&#xff1a; uname -rcat /etc/os-rele…...

海云安高敏捷信创白盒SCAP入选《中国网络安全细分领域产品名录》

近日&#xff0c;嘶吼安全产业研究院发布《中国网络安全细分领域产品名录》&#xff0c;海云安高敏捷信创白盒&#xff08;SCAP&#xff09;成功入选软件供应链安全领域产品名录。 在数字化转型加速的今天&#xff0c;网络安全已成为企业生存与发展的核心基石&#xff0c;为了解…...