最大子段和
问题描述
给出一个首尾相连的循环序列,从中找出连续的一段,使得该段中的数和最大。
输入格式
第一行一个整数 n, 表示有 n 个数。( 1<=n<=100000)
第二行有 n 个整数,每个数的绝对值不超过 100000.
输入样例
4
2 -4 1 4
输出样例
7
无力吐槽
题解
如果你选择用单调队列,恭喜你,对了,但是我们选择巧解
我们用sum[l,r]表示[l,r]的区间和
对于这道题目,有几个值得思考的结论
①如果不是首尾相连的循坏序列,那么最大子段和的子段[l,r]中对于任意的l,mid,sum[l,mid]>=0
证明:
如果存在[l,mid]<0,那么最大子段和可以更新成sum[l,r]-sum[l,mid]>sum[l,r]
②如果是首尾相连的循环序列,那么最大子段和=max(sum[1,n]-最小子段和,sum[1,n])
简单来讲这个结论就是
当最小子段和>=0时,最大子段和=sum[1,n]
当最小子段和<0时,最大子段和=sum[1,n]-最小子段和
证明:
当 最小子段和>=0 时就表示当前数列中所有值非负,自然而然最大子段和就是整个区间之和
当 最小子段和<0 时:
如果存在多个小于0的子段和,那么我们选择最小的
例如 sum[a,b]<sum[c,d]<0,a<b<c<d
因为sum[a,b]<0而sum[b,d]>=0,所以sum[a,d]=sum[a,b]+sum[b+c]>sum[a,b]
因此sum[a,d]的最大转圈(循环)子段和=sum[a,d]-sum[a,b]
而如果sum[1,n]减去其它的任意负子段和,增加的值都不如减去最小子段和多
因此对于这道题,我们的做法如下
①求出最大不循环子段和(sum<0就重新计数)
②求出最小不循环子段和(sum>0就重新计数)
③记录最大的单个值
④记录整个区间的和
当最大不循环子段和==0,那么输出最大单值
否则输出max(最大不循环子段和,整个区间和-最小不循环子段和)
解完
附上对拍代码
#include <iostream>
#include <cstdio>
using namespace std;
inline long long input()
{
char c=getchar();long long o;bool f=0;
while(c>57||c<48)f|=(c=='-'),c=getchar();
for(o=0;c>47&&c<58;c=getchar())o=(o<<1)+(o<<3)+c-48;
return f?-o:o;
}
long long x,zheng,fu,res_z,res_f,sum=0,all_fu=-987654321;
//rez_z记录最大不循环子段和,res_f记录最小不循环子段和的相反数
//zheng记录当前子段最大和,fu记录当前子段最小和的相反数
//sum记录所有值之和
//all_fu记录最大单值(在所有值都为负时输出)
int main()
{
freopen("maxsum.in","r",stdin);
freopen("maxsum.out","w",stdout);
long long n=input();
for(int i=1;i<=n;i++)
{
x=input();zheng+=x;fu-=x;
if(zheng<0)zheng=0;res_z=max(res_z,zheng);
if(fu<0)fu=0;res_f=max(res_f,fu);
sum+=x;
all_fu=max(x,all_fu);
}
printf("%lld",res_z>0?max(res_z,sum+res_f):all_fu);
}