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);
}
發佈了79 篇原創文章 · 獲贊 15 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章