NKOJ-Unknow 最大子段和

来源:互联网 发布:windows pe官方下载 编辑:程序博客网 时间:2024/06/05 00:14

最大子段和
问题描述

给出一个首尾相连的循环序列,从中找出连续的一段,使得该段中的数和最大。

输入格式

第一行一个整数 n, 表示有 n 个数。( 1<=n<=100000)第二行有 n 个整数,每个数的绝对值不超过 100000.

输入样例

42 -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);}
原创粉丝点击