算法设计与分析 SCAU17089 最大m子段和
创始人
2024-02-11 09:15:52
0

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 
#include 
#include 
#include 
/*
6 3
2 3 -7 6 4 -57 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
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前端成长记录

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
客厅放八骏马摆件可以吗(家里摆... 今天给各位分享客厅放八骏马摆件可以吗的知识,其中也会对家里摆八骏马摆件好吗进行解释,如果能碰巧解决你...
美团联名卡审核成功待激活(美团... 今天百科达人给各位分享美团联名卡审核成功待激活的知识,其中也会对美团联名卡审核未通过进行解释,如果能...