NKOJ-Unknow 最大子段和

最大子段和
问题描述

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

输入格式

第一行一个整数 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);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值