博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
洛谷P1115(最大子段和问题)题解(枚举+枚举优化+分治+dp比较)
阅读量:5011 次
发布时间:2019-06-12

本文共 3140 字,大约阅读时间需要 10 分钟。

P1115 最大子段和

题目描述

给出一段序列,选出其中连续且非空的一段使得这段和最大。

输入格式

第一行是一个正整数N,表示了序列的长度。

第二行包含NN个绝对值不大于10000的整数Ai,描述了这段序列。

输出格式

一个整数,为最大的子段和是多少。子段的最小长度为1。

输入输出样例

输入 
7
2 -4 3 -1 2 -4 3
输出
4

说明/提示

【样例说明】

2,-4,3,-1,2,-4,3中,最大的子段和为4,该子段为3,-1,2.

【数据规模与约定】

对于40%的数据,有N2000。

对于100%的数据,有N200000。

各个方法介绍:

纯暴力解法:

这个方法没什么好说的,直接枚举边界然后在累加每一个,判断和是否为最大,最大替换即可,算法时间复杂度O(N3)。

代码:

#include 
int a[200000];int main (void){ int n, i, j, sum, max, start; max = -1e9; scanf("%d", &n); for(i = 0; i < n; i++) scanf("%d", &a[i]); for(i = 0; i < n; i++) for(j = i; j < n; j++) { sum = 0; for(start = i; start <= j; start++) sum += a[start]; if(sum > max) max = sum; } printf("%d", max); return 0;}

 

  结果:

 

 

暴力优化:

由于遍历范围的头尾两个的位置时,尾部要从头开始每次循环完递增,所以,我们实际上可以在递增时把值加起来,判断是否最大,算法时间复杂度O(N2)

代码:

#include 
int a[200000];int main (void){  int n, i, j, sum, max, start;  max = -1e9;  scanf("%d", &n);  for(i = 0; i < n; i++)     scanf("%d", &a[i]);  for(i = 0; i < n; i++) {
    sum = 0;    for(j = i; j < n; j++)    {      sum += a[j];   if(sum > max)     max = sum;   }  }  printf("%d", max);  return 0;}

 

  

 

结果:

分治法:

所谓分治,就是强行把数拆成两半,那么,我们易得,该最大子序列一定是在前半段的最大子序列或者后半段的最大子序列或者是前半段子序列末尾为前半段范围末尾的最大子序列与后半段子序列开头为后半段范围开头的最大子序列之和,只可能是这三种情况。我们采用递归来实现分治,每次程序返回的都是在该段内最大的子序列,最后就能实现在整段内找到最大的子序列。我们人为的把数组拆成两半,通过递归得到左段和右段的最大值,然后从该段中间向两边遍历,找到从该范围内中间到左边和中间+1到右边的最大子序列,并把两者相加,并把其与左右段最大子序列进行比较并返回最大即可得到该段中最大的子序列。算法时间复杂度O(NlogN)。

代码:

#include 
int a[200000], max;int maxn(int x, int y, int z){ if(x >= y && x >= z) return x; else if(y >= x && y >= z) return y; else return z;}int divide(int left, int right){ if(left == right) return a[left]; int l = 0, r = 0, maxlt = -1e9, maxrt = -1e9;//maxrt为从中间后一个开始向右最大的子序列,maxlt从中间向左的最大 int mid = (right + left) / 2; int maxl = divide(left, mid );// maxl为左段最大值 int maxr = divide(mid + 1, right);//maxr为右端最大值 for(int i = mid; i >= left; i--) { l += a[i]; if(l > maxlt) maxlt = l; } for(int i = mid + 1; i <= right; i++) { r += a[i]; if(r > maxrt) maxrt = r; } return maxn(maxl, maxr, maxlt + maxrt);}int main (void){ int n, i, j; scanf("%d", &n); for(i = 0; i < n; i++) scanf("%d", &a[i]); max = divide(0, n - 1); printf("%d", max); return 0;}

 

  

 

 

结果:

动态规划:

本题将序列从开头开始遍历,相当于序列的开头为数组的开头,序列的末尾从开头一直到末尾,每次得到的结果为该范围内最大的子序列。一开始最大子序列和就是a[0](因为序列开头和结尾都是第一个)。当一个序列中找到最大子序列后,读取下一个数,max要么是之前序列的最大值,要么是之前序列中子序列的末尾是之前序列末尾的最大子序列加上刚刚读取的数要么就是刚刚读取的数。如果之前序列中子序列的末尾为之前序列末尾的最大子序列小于0,那么该子序列加上刚刚读取的数肯定没有刚刚读取的数大,如果大于等于0,则该子序列加上刚刚读取的数要比刚刚读的数大,所以两者最大与之前序列最大的子序列之和比较,得到现在的最大子序列和,当到达末尾时,也就是这个数组的最大子序列。声明一个sum累加数,每加一个数都要判断sum是否比max要大,如果大就替换,如果sum < 0, 那么说明从此时的开始累加比此时的数加上之前累加出来的负值要大,因此将sum置为0(表明从此时开始算子序列,sum的值为当前的数),sum的值即是从当前位置向左得到的子序列中和最大的,因而与max进行比较。算法时间复杂度O(N)。

代码:

#include 
int a[200000], sum, max;int main (void){ int n, i; max = -1e9; scanf("%d", &n); for(i = 0; i < n; i++) { scanf("%d", &a[i]); if(sum < 0)//说明之前的序列中,子序列末尾为之前序列末尾的最大子序列之和为负数,加a[i]还不如a[i]大,所以置为0 sum = 0; sum += a[i]; if(sum > max) max = sum; } printf("%d", max); return 0;}

 

  

 

结果:

其实采用dp的方法并不需要开数组进行存储,这样既节省了空间也节省了时间,这里只需要把a[i]换成一个变量即可(就不贴代码了)

转载于:https://www.cnblogs.com/jacobfun/p/11262551.html

你可能感兴趣的文章
综合练习:词频统计
查看>>
中文url编码乱码问题归纳整理一
查看>>
Cesium应用篇:3控件(3)SelectionIndicator& InfoBox
查看>>
58. Length of Last Word(js)
查看>>
前端面试题汇总(持续更新...)
查看>>
如何成为F1车手?
查看>>
QT自定义消息
查看>>
Save (Not Permitted) Dialog Box
查看>>
装饰模式(Decorator)
查看>>
任务13:在Core Mvc中使用Options
查看>>
利用Excel 2010数据透视图实现数字的可视化的图形直观展示
查看>>
Sort Colors
查看>>
iview树的修改某个节点,树刷新后自动展开你刚才展开的所有节点
查看>>
oracle服务起不来以及无法监听问题解决
查看>>
Mvc--Html.ActionLink()的用法
查看>>
delphi 基础书籍推荐
查看>>
《面向对象程序设计》2018年春学期寒假及博客作业总结
查看>>
iOS开发UI之KVC(取值/赋值) - KVO (观察某个对象的某个属性的改变)
查看>>
1.7 将一个MxN矩阵所有为0的元素所在行和列全部置0
查看>>
删除U盘时提示无法停止‘通用卷’设备的解决方法!!不要每次都硬拔了,对电脑有不小的损害!!!...
查看>>