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

news2024/12/23 13:47:32

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 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";
	}
	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) {
	//只有一堆石子,返回 0
	if(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) {
	//只有一堆石子,返回 0
	if(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;
		}
		//一堆石子的值为 0
		dp[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. 总结

沉淀过程是一种修行。

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

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

相关文章

中国区域地表净辐射数据集(1982-2017)

摘要 地表净辐射(Rn) 是陆地生态系统水、热、碳循环过程中的重要参数,准确地估算Rn并分析其时空变化特征对气候变化和能量平衡等研究具有重要意义。本文基于CMFD再分析资料和GLASS地表反照率数据集估算1982~2017年中国区域逐日Rn。本数据集将逐日Rn统计为每月Rn,空间分辨率…

诚迈科技携数智成果亮相南京软博会,交出数实融合圆满答卷

8月20-23日&#xff0c;南京软博会在南京国际博览中心盛大举行。诚迈科技聚焦数字中国建设需求和万物智联时代关键软件技术发展趋势&#xff0c;全方位展示了一系列操作系统产品和数智化解决方案成果&#xff0c;旨在推动数实融合创新。此外&#xff0c;诚迈科技受邀出席中国工…

【业务功能篇74】三高微服务项目springboot-springcloud

三高指的是&#xff1a;高性能、高并发、高可用 2.项目架构 2.1 系统架构图 整体的项目架构图如下 2.2 业务组成 整体的项目业务组成如下

Elasticsearch简介及安装

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

指针与引用详解

博文内容&#xff1a; 指针与引用 指针和引用的区别&#xff1f; 这个问题主要还是仁者见仁&#xff0c;智者见智。 主要就是对于俩者的出现时间来看&#xff0c;对于指针来说&#xff0c;早在C语言那会就已经出现&#xff0c;C是后于C语言的&#xff0c;所以C的出现会多少在使…

美创科技“签”手柠檬文才学堂,共推高校数据安全建设

近日&#xff0c;由柠檬文才学堂联合中国教育在线、东北财经大学网络教育学院共同主办的“三教统筹下高校继续教育数字化转型研讨”顺利召开。 国内高等院校&#xff08;高职院校&#xff09;继续教育分管领导&#xff0c;继续教育学院领导及继续教育信息化、教学教务管理、课程…

❤ 给自己的mac系统上安装java环境

❤ 给自己的mac系统上安装java环境 &#x1f353; 作为前端工程师如何给自己的mac系统上安装java环境 &#x1f34e; 最近因为自己的一些项目需求&#xff0c;mac电脑上需要安装一些后台的java环境&#xff0c;用来跑后台的java程序&#xff0c;于是从一个前端工程师的角度安…

00-音视频-概述

有很多场合会使用的音视频&#xff0c;比如安防、视频闸机、影音播放器、视频通话&#xff0c;短视频等等。 从摄像头采集到用户观看&#xff0c;这中间涉及到了很多技术。 用户一般观看的高清视频1080P30帧。若按24位RGB对视频进行存储&#xff0c;一个60分钟视频所占空间 …

IBM Spectrum LSF Data Manager

IBM Spectrum LSF Data Manager 当需要大量数据来完成计算时&#xff0c;您的应用程序最好不受阻碍地访问与应用程序执行环境相关的数据位置所要求的数据。 LSF Data Manager 通过将所需数据 登台 以尽可能接近应用程序站点来解决数据局部性问题。 许多应用在几个领域需要大量…

在Linux系统中配置代理服务器来加速软件包管理

作为一名专业程序员&#xff0c;我今天要和大家分享一个在Linux系统中配置代理服务器来加速软件包管理的解决方案。如果你经常在Linux上使用软件包管理器&#xff08;如apt、yum等&#xff09;&#xff0c;但下载速度缓慢&#xff0c;那么本文将给你带来一些操作方法&#xff0…

《基础教育论坛》期刊简介及投稿要求

《基础教育论坛》杂志是经国家新闻出版总署批准、国内外公开发行的综合性教育学术期刊。作者可通过&#xff0c;中华人民共和国新闻出版总署的网站进行出版许可及刊号的查询。 2009年&#xff0c;《基础教育论坛&#xff08;综合版&#xff09;》杂志创刊。 2012年&#xff0…

电阻计算公式

我正在「拾陆楼」和朋友们讨论有趣的话题&#xff0c;你⼀起来吧&#xff1f; 拾陆楼知识星球入口

Powershell NTP Server Windows 7,8,10,11,2012,2016,2019,2022

NTP前言 NTP服务器是用来使计算机时间同步化的一种协议,它可以使计算机对其服务器或时钟源(如石英钟、GPS等)做同步化,提供高精准度的时间校正(LAN上与标准间差小于1毫秒,WAN上几十毫秒),且可介由加密确认的方式来防止恶毒的协议1。 ntp 参考 w32tmpowershell参考 参考…

激光雷达的「新」难题

车载激光雷达赛道正在进入关键时刻。 本周&#xff0c;又一家造车新势力—极石汽车旗下首款车型极石01正式对外发布&#xff0c;高阶智驾配置三颗来自禾赛的激光雷达&#xff0c;包括1个128线前向激光雷达和2个侧向纯固态激光雷达。 在此之前&#xff0c;中国市场已经量产交付的…

探索归并排序:分而治之的排序艺术

1. 引言&#xff1a;排序算法的重要性与背景 排序是计算机科学中的基础问题之一&#xff0c;它在各种应用中都得到了广泛的应用&#xff0c;从搜索引擎到数据库管理系统。而归并排序&#xff08;Merge Sort&#xff09;作为一种经典的排序算法&#xff0c;通过分治法的思想&am…

Python“牵手”京东工业商品详情数据采集方法,京东工业商数据API申请步骤说明

京东工业平台介绍 京东工业平台是京东集团旗下的一个B2B电商平台&#xff0c;主要面向企业客户提供一站式的采购服务。京东工业平台依托京东强大的供应链和配送能力&#xff0c;为企业用户提供全品类、全渠道、全场景的采购解决方案&#xff0c;涵盖电子元器件、机械配件、办公…

smartbi token回调获取登录凭证漏洞

2023年7月28日Smartbi官方修复了一处权限绕过漏洞。未经授权的攻击者可利用该漏洞&#xff0c;获取管理员token&#xff0c;完全接管管理员权限。 于是研究了下相关补丁并进行分析。 0x01分析结果 依据补丁分析&#xff0c;得到如下漏洞复现步骤 第一步&#xff0c;设置Engi…

java开源 VR全景商城 saas商城 b2b2c商城 o2o商城 积分商城 秒杀商城 拼团商城 分销商城 短视频商城 小程序商城搭建 bbc

​ 1. 涉及平台 平台管理、商家端&#xff08;PC端、手机端&#xff09;、买家平台&#xff08;H5/公众号、小程序、APP端&#xff08;IOS/Android&#xff09;、微服务平台&#xff08;业务服务&#xff09; 2. 核心架构 Spring Cloud、Spring Boot、Mybatis、Redis 3. 前…

FPGA功能及特点

集成电路芯片包括数字芯片和模拟芯片两大类&#xff0c;数字芯片又分为存储器芯片和逻辑芯片。 逻辑芯片一般包括CPU、GPU、DSP等通用处理器芯片以及专用集成电路芯片ASIC。 FPGA&#xff08;现场可编程门阵列&#xff09;就是逻辑芯片的一种。 FPGA功能 FPGA中文名是现场可…

【速成】蓝桥杯嵌入式省一教程:(十)利用共用体进行E2PROM读写

在上一节中我们了解到&#xff0c;AT24C02芯片&#xff08;E2PROM存储器&#xff09;内部含有256个8位字节&#xff0c;每一次只能对一个字节进行读写操作。因此&#xff0c;其只能处理unsigned char或uint8_t类型的数据&#xff0c;对于int、float型等大于占用一个字节的数据&…