题目描述
给出一段序列,选出其中连续且非空的一段使得这段和最大。
输入描述
第一行是一个正整数 N ( 1 ≤ N ≤ 200000 ) ,表示了序列的长度。
接下来的 N 行包含 N 个绝对值不大于 10000 的整数 A [ i ] ,描述了这段序列。
输出描述
仅包括 1 个整数,为最大的子段和是多少,子段的最小长度为 1 。
样例输入
7
2
-4
3
-1
2
-4
3
样例输出
4
Hint
Origin: Sidney
Edit by stdKonjac in 2020
解题思路:
关于求子区间求和的问题,现在已经有很多套路,比如线段树和树状数组,因为这俩都是通过子区间划分后自定向下形成的树。但是毕竟是动态规划专题,还是不要背模板了。
说到划分,最著名的题就是整数划分问题,只是整数划分问题是从一个整数划到区间长度=1-n,而现在这个问题是从区间求和返回整数,相当于一个逆过程,
那么划分嘛,反过来就是归并,总体还是分治思想啊,所以,先复习一下分治法最有名的归并排序算法,
贴个链接:https://www.cnblogs.com/KID-yln/p/12636211.html
和归并一样,现在有两种思路,
- 第一种直接对区间划分,整体使用搜索递归,自顶向下,得到划分的区间脚标,然后返回一个求和,最好是能实现之前小期间的和能被后来的大区间利用到
- 第二种直接规定区间长度可为1-n,利用循环,仿照非递归实现的归并算法,每次得到脚标的起点和终点然后求和,具体优化可以仿照滑动窗口
因为是动态规划—递推专题,先说一下第二种做法:
仿照滑动窗口,规定一个窗口大小从1-n的窗口,每次移动一个位置就一加一减,
进一步最好区间长度i从大到小遍历,并且把第i项第一个求和存下来,后面使用,
运算次数不超过O(n^2),假设CPU运算次数为10^(-10)s一次,总耗时大概不会超过1s,有点勉强,可以试一试
(依照CPU i5 8400循环做 i-- ; 10亿次大概要1.2秒左右(10 0000 0000 一次减 1 减 10亿次到 0为止,该时间为循环延时时间+运算时间,循环延时时间占大头)
试了以下,运气很好,过了,,不到100ms,不知道是不是cpu比较快
以下为代码:
1 #include <iostream> 2 #include <cstdio> 3 //#include <cstring> 4 #include <algorithm> 5 //#include <numeric> 6 using namespace std; 7 typedef long long ll; 8 const int M=2*1e5+10; 9 int n; 10 11 ll a[M]; 12 13 ll sum=0; 14 ll S(int from,int to) { 15 ll t=0; 16 for(int i=from; i<=to; i++)t+=a[i]; 17 return t; 18 } 19 20 void f() { 21 int n1=n; 22 ll sum1=S(1,n); 23 sum=sum1; 24 while(n1) { 25 int l=0; 26 int r=1+n1-1; 27 sum1-=a[r]; 28 ll ans=sum1; 29 while(r<=n) { 30 ans-=a[l]; 31 ans+=a[r]; 32 sum=max(sum,ans); 33 //printf("%lld ",ans); 34 l++; 35 r++; 36 } 37 //puts(""); 38 n1--; 39 } 40 printf("%lld\n",sum); 41 } 42 43 44 45 int main() { 46 scanf("%d",&n); 47 for(int i=1; i<=n; i++)scanf("%lld",&a[i]); 48 a[0]=0; 49 f(); 50 51 52 return 0; 53 }
现在回头来说一下第一种做法:
如果直接递归,主要可以利用循环1-n,内套一个栈,设置栈深,每种情况都重新算一次,
总共有n(n+1)/2次求和,运算次数达到了Σ(n-i)*i => O(n^3)看这个数量级为200000,指定TE,
那么想要优化就需要简化运算次数,至少需要利用到之前的求和,保存之前的求和值作为节点,那其实就是建—线段树OR树状数组—然后求某些分支节点的和,
其实这也是分治,只不过是把分治的内容化成了一棵树,数量级约为O(n*logn);
(占个坑,后面补上树状数组/线段树的做法)
原文地址:https://www.cnblogs.com/KID-yln/p/12536379.html