【8月刷题打卡】环状最大两段子段和

256 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第21天,点击查看活动详情

环状最大两段子段和

题目描述

给出一段长度为 nn 的环状序列 aa,即认为 a1a_1ana_n 是相邻的,选出其中连续不重叠且非空的两段使得这两段和最大。

输入格式

第一行是一个整数 nn,表示序列的长度。

第二行有 nn 个整数,描述序列 aa,第 ii 个数字表示 aia_i

输出格式

一行一个整数,为最大的两段子段和是多少。

样例 #1

样例输入 #1

7
2 -4 3 -1 2 -4 3

样例输出 #1

9

提示

数据规模与约定

对于全部的测试点,保证 2n2×1052 \leq n \leq 2 \times 10^5104ai104-10^4 \leq a_i \leq 10^4

对于这个问题,由于分成了两个子序列,我们不妨就是枚举一下可能出现的情况:

无非就这两种:

1.+++++0000+++++0000++++

2.0000++++00000++++000000

0就表示选了这个数,+就表示不选这个数,

那我们正反先做一个普通的最大子序列,就可以完成第2中情况,然后再求个最小子序列,把总和一减就是第1种情况。

至于求最小子序列,可以把数字都负过来,然后再搞个最大子序列就好了。

楼下举的例子非常对,我仔细思考了一下这个特例,即

4 -1 1 -1 -1

当我们将数字负过来时,就成了:

4 1 -1 1 1

此时的两个最大子序列我们会选成:

0+00 而首尾都选了,其实就是只选了一个序列,而这种特殊情况又只会在

只有一个数是正数时出现,所以特判一下就好了,如果只有一个数是正数,我们就不做将数字负过来后的那个最大子序列了,

直接找两个最大的数累和就行了。

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int INF=0x3f3f3f3f;
const int N=2e5+5;
int n,f[N],g[N],a[N],sum=0,tot=0;
int query(){
    int res=-INF;
    for (int i=1;i<=n;i++)f[i]=max(f[i-1],0)+a[i];
    for (int i=n;i>0;i--)g[i]=max(g[i+1],0)+a[i];
    for (int i=1;i<=n;i++)f[i]=max(f[i-1],f[i]);
    for (int i=n;i>0;i--)g[i]=max(g[i+1],g[i]);
    for (int i=1;i<n;i++)res=max(res,f[i]+g[i+1]);
    return res;
}
int main(){
    scanf("%d",&n);
    memset(f,~0x3f,sizeof(f));memset(g,~0x3f,sizeof(g));
    for (int i=1;i<=n;i++)scanf("%d",&a[i]),sum+=a[i],tot+=a[i]>0;
    int t1=query();
    if (tot==1){
        printf("%d",t1);
    }else{
        for (int i=1;i<=n;i++)a[i]=-a[i];
        int t2=sum+query();
        if (!t2)t2=-INF;
        printf("%d",max(t1,t2));
    }
    return 0;
}