算法设计与分析 SCAU17089 最大m子段和

news2024/11/10 17:19:30

17089 最大m子段和

时间限制:1000MS 代码长度限制:10KB
提交次数:0 通过次数:0

在这里插入图片描述

题型: 编程题 语言: G++;GCC;VC;JAVA

Description

“最大m子段和”问题:给定由n个整数(可能为负)组成的序列a1、a2、a3、…、an,以及一个正整数m,
要求确定序列的m个不相交子段,使这m个子段的总和最大!

m是子段的个数。当m个子段的每个子段和都是负值时,定义m子段和为0。


输入格式

第一行:n和m; (n,m<10000)
第二行:n个元素序列,中间都是空格相连。

比如:
6 3
2 3 -7 6 4 -5


输出格式

输出最大m子段和。

比如:
15

这15可由这三个段之和来的:(2 3) -7 (6) (4) -5


输入样例

6 3
2 3 -7 6 4 -5


输出样例

15


解题思路

1. dp 方程定义

定义二维数组 dp, dp[ i ][ j ],表示前 j 项所构成 i 子段的最大和,且必须包含着第 j 项,即以第 j 项结尾


2. 状态转移方程

求 dp[ i ][ j ],有两种情况

  1. dp[ i ][ j ] = dp[ i ] [ j-1 ] + a[ j ] ,即把第 j 项融合到第 j-1 项的子段中,子段数没变(即跟前面连成一段,一般发生于前面的数是正数时);
  2. dp[ i ][ j ] = dp[ i-1 ] [ k ] + a[ j ],(i-1<= k < j ),把第 j 项作为单独的一个子段,然后找一下 i - 1 个子段时,最大的和,然后加上a[ j ]

对于第二点,思考方式可以是如下图:当你遍历到6这个元素时,很明显不能选择前面的元素(因为是负的),那么就选择其他(尽量正数),于是目光自然就看到了前面那一份 2 + 3 = 5。
而 k 的取值范围又如何解释呢

  1. 大于等于 i - 1 ,是因为 i - 1 个数字最多只能被分为 i - 1 份,即每份一个数字;
  2. 而小于 j 即最多可以选择到第 j - 1 个数字,是因为当你在抉择第 j 个数字如何划分范围时,前面只有 j - 1 个数字给你划分,范围怎么可能能跑到 j 自己及后面去。

在这里插入图片描述
然后比较上面两种情况,取大的即可。

下面看图,红色数字为输入的序列:

动态规划,借助矩阵可以直观的看到计算过程。
如图,要求dp[ 3 ][ 6 ],只需比较 他左边的那个,和上面那一行圈起来的之中 最大的数(i-1 <= t <= j),
再加上 a[ j ] 即为 dp[ 3 ][ 6 ] 的值。


3. 算法解题思路

  1. 初始化 dp 数组,前 0 个数字划分为任意份肯定都为0:dp[i][0] = 0; 任意个数字划分为0份肯定也为0;n 个数字肯定无法划分为 n - 1 份,最多 n 份即每份一个数字:dp[i][i - 1] = -INF(赋值为最小数即可)。
  2. 外面双层循环,第一层 i 是记录划分到第几份,第二层 j 是记录到是把前 j 个数字划分为 i 份。
  3. 在每次划分时,都要去上一份那里去寻找最大值,看看上一份中使用哪几个数字能划分成最大值 maxNum,再跟当前份数的前一个数字来比较:dp[i][j - 1],看看第 j 个数到底是继续划分到前一个数字后面,还是说单独开一份。
  4. 那么 dp[i][j] 就等于第三个步骤中大的那个,再加上当前数字的大小,即为最大和。
  5. 最后,也就是最关键的地方,输出的找法,不是单纯的 dp[m][n],因为最大m字段和不一定包括全部数,可以舍弃掉一些数比如负数,见如下:

在划分为 m 份时进行寻找,看看使用前几个数字可以找到最大字段和,比如说 5 6 7 -1 2 -8 -9,最大字段和肯定不可能把最后两位划分进去,所以代码应为如下

    // 找划分成 m 份时,究竟前几个数字才能凑成最大字段和
    int res = -INF;
    // 由于是划分为 m 份,所以数字至少得 m 个才能,总不可能2个数字划分成3份吧
    for(i = m; i <= n; i++) {
        res = max(res, dp[m][i]); 
    }

4. 可优化点

  1. 沿着第 m 行的最后一个元素,往左上方向画一条线,线右上方的元素是没必要计算的
    那么 dp[ i ][ j ] ,j++ 的时候,j 的上限为 i + n - m 即可。
    还有左下角那一半矩阵,也是不用计算的,因为1个数字不可能分为2个子段
  2. 每确定一个 dp[ i ][ j ],只需用到 本行和上一行,所以不用开维数组也可以,省内存。
    开两个一维数组,pre 和 dp,pre 记录上一行,dp 记录当前行
  3. 再对上一行红圈中的数字找最大值时,若用一个循环来找,容易超时。所以优化方法是:在每次计算 dp 之前,同时记录下j前面的最大元素。

时间复杂度大致为O(m*(n-m+1)),mn-m方

通过图片,分析情况1和2,就能发现,从左上角走到第 m 行的最后一个元素即可,找出第 m 行的最大值即为答案。



更多注释可查看下方的完整代码中,有助于理解

代码如下

#include <iostream>
#include <cmath>
#include <string.h>
#include <algorithm>
/*
6 3
2 3 -7 6 4 -5

7 7
-2 11 -4 13 -5 6 -2
*/

using namespace std;

const int INF=0x3f3f3f3f;

int a[10001];
int dp[10001][10001]; // dp[i][j],表示前 j 项所构成 i 子段的最大和,且必须包含着第j项,即以第j项结尾

int main()
{
    // 此题为求最大上升子序列的变种,是求多个
    int i, j, k, n, m;
    cin >> n >> m;

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

    for(i = 1; i <= n; i++) {
        dp[i][0] = 0;
        dp[0][i] = 0;
        dp[i][i - 1] = -INF; // 每一行开头,即 n 个数字想分成 n - 1 份是不可能,赋值为最小数即可
    }

    // 分成 i 段时
    for(i = 1; i <= m; i++) {
        // 前 j 个数
        for(j = i; j <= n; j++) {
            int maxNum = -INF;
            // 去上一行找最大值,索引结束于 j - 1 之前
            for(k = i - 1; k < j; k++) {
                if(dp[i - 1][k] > maxNum) {
                    maxNum = dp[i - 1][k];
                }
            }
            dp[i][j] = max(dp[i][j - 1], maxNum) + a[j];
        }
    }

    // 找划分成 m 份时,究竟几个数字才是最好的
    int res = -INF;
    // 由于是划分为 m 份,所以数字至少得 m 个才能,总不可能2个数字划分成3份吧
    for(i = m; i <= n; i++) {
        res = max(res, dp[m][i]);
    }

    cout << res << endl;

    return 0;
}

优化后代码

#include<bits/stdc++.h>
using namespace std;  
typedef long long ll;
const int N=1e6+5; 
const int INF=0x3f3f3f3f;
int n,m;
ll a[N],dp[2][N];   //只保存上一行和当前行 
int main()  
{
    while(~scanf("%d%d",&n,&m))   //n个数字,m子段和 
    { 
        for(int i=1;i<=n;i++) 
			scanf("%lld",a+i);  
		for(int i=0;i<=n;i++)
			dp[0][i]=0,dp[1][i]=0;    //关键!此题答案只允许正值
		
        for(int i=1,k=1;i<=m;i++,k^=1)    //分为i段,k为两行之间的切换
        {
        	dp[k][i-1]=-INF;   //i==j时,杜绝与前一元素共伍
            ll maxpre=-INF;    //maxpre记录上一行的最大值
            for(int j=i;j<=n-m+i;j++)
            {
                maxpre=max(maxpre,dp[k^1][j-1]);     //随时更新上一行最大值
                dp[k][j]=max(dp[k][j-1],maxpre)+a[j]; //*对情况1、2的选择
            }
        }
        ll ans=-INF;
        for(int i=m;i<=n;i++)   //找到第m行的最大值,即为答案
                ans=max(ans,dp[m&1][i]);
        printf("%lld\n",ans);
    }  
}


最后

对我感兴趣的小伙伴可查看以下链接

  • 我的掘金主页:https://juejin.cn/user/1302297507801358
  • 博客主页:http://blog.zhangjiancong.top/
  • 公众号:Smooth前端成长记录

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

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

相关文章

【Java】初识IO流【附导航】

文章目录01 什么是IO02 数据源03 什么是流04 IO流原理⇩➩ 导航01 什么是IO 对于任何程序设计语言而言&#xff0c;输入输出&#xff08;Input / Output&#xff09;系统都是非常核心的功能。程序运行需要数据&#xff0c;数据的获取往往需要跟系统外部进行通信&#xff0c;外部…

论文复现|Panoptic Deeplab(全景分割PyTorch)

摘要&#xff1a;这是发表于CVPR 2020的一篇论文的复现模型。本文分享自华为云社区《Panoptic Deeplab(全景分割PyTorch)》&#xff0c;作者&#xff1a;HWCloudAI 。 这是发表于CVPR 2020的一篇论文的复现模型&#xff0c;B. Cheng et al, “Panoptic-DeepLab: A Simple, Str…

63. 不同路径 II

题目 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish”&#xff09;。 现在考虑网格中有障碍物。那么从左上角到…

MySQL主/从-主/主集群安装部署

MySQL集群架构的介绍 我们在使用到MySQL数据库的时候&#xff0c;只是一个单机的数据库服务。在实际的生产环境中&#xff0c;数据量可能会非常庞大&#xff0c;这样单机服务的MySQL在使用的时候&#xff0c;性能会受到影响影响。并且单机服务的MySQL的数据安全性也会受到影响…

数字信号处理-09-串行FIR滤波器MATLAB与FPGA实现

前言 本文介绍了设计滤波器的FPGA实现步骤&#xff0c;并结合杜勇老师的书籍中的串行FIR滤波器部分进行一步步实现硬件设计&#xff0c;对书中的架构做了简单的优化&#xff0c;并进行了仿真验证。 FIR滤波器的FPGA实现步骤 从工程角度分析FIR滤波器的FPGA实现步骤如下&…

Vim简洁教程

Vim简洁教程Vim简介使用方法命令模式输入模式底线命令模式模式转换使用流程Vim键盘图Vim简介 在Linux系统中&#xff0c;Vim是一款自带的文本编辑器&#xff0c;因此Vim常用于Linux系统中。Vim是从 vi 发展出来的&#xff0c;包含代码补全、编译及错误跳转等方便编程的功能&am…

【LeetCode每日一题】——78.子集

文章目录一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【解题思路】七【题目提示】八【时间频度】九【代码实现】十【提交结果】一【题目类别】 数组 二【题目难度】 中等 三【题目编号】 78.子集 四【题目描述】 给你一个整数数组 nums &…

亚太C题详细版思路修改版(精)

今年的亚太A、B题的感觉难度不低&#xff0c;其难度已经可以与电工妈杯这种比赛的赛题难度相提并论了。因此&#xff0c;这次预计选C题的人数可能不少&#xff0c;这对于大家来说也是个好消息。塞翁失马焉知非福&#xff0c;难对于大家来说都难&#xff0c;只要自己放平心态&am…

计算机组成原理习题课第一章-1(唐朔飞)

计算机组成原理习题课第一章-1&#xff08;唐朔飞&#xff09; ✨欢迎关注&#x1f5b1;点赞&#x1f380;收藏⭐留言✒ &#x1f52e;本文由京与旧铺原创&#xff0c;csdn首发&#xff01; &#x1f618;系列专栏&#xff1a;java学习 &#x1f4bb;首发时间&#xff1a;&…

【Pygame实战】这游戏有毒,刷爆朋友圈:小编已与病毒版贪吃蛇大战了三百回合,最高分339?

导语 Hello&#xff0c;大家好呀&#xff01;我是木木子吖&#xff5e; 一个集美貌幽默风趣善良可爱并努力码代码的程序媛一枚。 听说关注我的人会一夜暴富发大财哦~ &#xff08;哇哇哇 这真的爱&#x1f60d;&#x1f60d;&#xff09; 所有文章完整的素材源码都在&#…

Android中JVM七大垃圾收集器【解析】

概述 GC垃圾收集器的种类 新生代&#xff1a;年轻代用来存放最近创建的对象老年代&#xff1a;主要存放应用程序中生命周期长的内存对象永久代&#xff1a;内存的永久保存区域&#xff08;类和元数据&#xff09;&#xff0c;GC不参与回收Serial收集器&#xff1a;串行收集器…

web网页设计—— 中国餐饮协会(HTML+CSS)

&#x1f380; 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

CentOS8使用阿里云yum源异常问题及解决方法

镜像下载、域名解析、时间同步请点击 阿里云开源镜像站 Linux安装git时发生如下错误 [rootraoyuuuu maven]# dnf install git Repository extras is listed more than once in the configuration Repository epel is listed more than once in the configuration CentOS-8 - B…

关于windows的文件监控管理系统(Java)

目 录 摘 要 I Abstract II 1.绪论 1 1.1课题背景 1 1.2系统开发的目的和意义 2 1.3国内外概况 3 1.4研究主要内容 3 2.windows文件监控管理系统相关技术介绍 4 2.1 API 4 2.2 API HOOK 5 2.3 Java 5 2.4 DLL 6 2.4 Windows系统的Socket编程 6 2.4.1使用WinSock API 6 2.4.2 使…

【 C++ 】IO流

目录 1、C语言的输入输出 2、流是什么 3、CIO流 3.1、C标准IO流 3.2、C文件IO流 文件操作步骤 以二进制的形式操作文件 以文本的形式操作文件 4、stringstream的介绍 1、C语言的输入输出 C语言中我们用到的最频繁的输入输出方式就是scanf()和printf()。 scanf()&#xff1a…

[前端基础] JavaScript 基础篇(下)

DOM 和 BOM DOM 指的是文档对象模型&#xff0c;它指的是把文档当做一个对象来对待&#xff0c;这个对象主要定义了处理网页内容的方法和接口。BOM 指的是浏览器对象模型&#xff0c;它指的是把浏览器当做一个对象来对待&#xff0c;这个对象主要定义了与浏览器进行交互的法和…

Node核心模块之Stream

Node.js诞生之初就是为了提高IO性能&#xff0c;文件操作系统和网络模块实现了流接口&#xff0c;Node.js中流就是处理流式数据的抽象接口。 那么应用程序为什么使用流来处理数据&#xff1f; 常见问题 同步读取资源文件&#xff0c;用户需要等待数据读取完成资源文件最终一次…

【Windows】windows10时间显示秒数

一般情况下windows10的电脑时间只显示小时和分钟&#xff0c;但是有的用户想要时间显示更加精细&#xff0c;那么windows10时间怎么显示秒呢&#xff1f;大家可以通过修改注册表的方式进行设置&#xff1a;打开注册表编辑器&#xff0c;定位到Advanced&#xff0c;右键新建DWOR…

【第十四篇】Camunda系列-多人会签【多实例】

多人会签 Multiple Instance 也叫多实例任务。 1.会签说明 多实例活动是为业务流程中的某个步骤定义重复的一种方式。在编程概念中,多实例与 for each 结构相匹配:它允许对给定集合中的每个项目按顺序或并行地执行某个步骤或甚至一个完整的子流程。 多实例是一个有额外属性…

注解(Annotation)

注解 注解也被称为元数据&#xff08;MateDate&#xff09;,用于修饰或解释包&#xff0c;类&#xff0c;方法&#xff0c;属性&#xff0c;构造器&#xff0c;局部变量等数据信息和注释一样&#xff0c;注解不会影响程序逻辑&#xff0c;但是注解可以被编译或者运行&#xff…