携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第21天,点击查看活动详情
环状最大两段子段和
题目描述
给出一段长度为 的环状序列 ,即认为 和 是相邻的,选出其中连续不重叠且非空的两段使得这两段和最大。
输入格式
第一行是一个整数 ,表示序列的长度。
第二行有 个整数,描述序列 ,第 个数字表示 。
输出格式
一行一个整数,为最大的两段子段和是多少。
样例 #1
样例输入 #1
7
2 -4 3 -1 2 -4 3
样例输出 #1
9
提示
数据规模与约定
对于全部的测试点,保证 ,。
对于这个问题,由于分成了两个子序列,我们不妨就是枚举一下可能出现的情况:
无非就这两种:
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;
}