环状最大两段子段和
题目描述
给出一段长度为 n n n 的环状序列 a a a,即认为 a 1 a_1 a1 和 a n a_n an 是相邻的,选出其中连续不重叠且非空的两段使得这两段和最大。
输入格式
第一行是一个整数 n n n,表示序列的长度。
第二行有 n n n 个整数,描述序列 a a a,第 i i i 个数字表示 a i a_i ai。
输出格式
一行一个整数,为最大的两段子段和是多少。
样例 #1
样例输入 #1
7
2 -4 3 -1 2 -4 3
样例输出 #1
9
提示
数据规模与约定
对于全部的测试点,保证 2 ≤ n ≤ 2 × 1 0 5 2 \leq n \leq 2 \times 10^5 2≤n≤2×105, − 1 0 4 ≤ a i ≤ 1 0 4 -10^4 \leq a_i \leq 10^4 −104≤ai≤104。
思路
-
首先,这道题相较于第 2 弹多了个环字,那么此时处理过程就不一样了。
-
然后我们发现本道题的规律:
-
不难发现,我们能选择的集合只有下面两种情况:
1. ( 000 ) 111 ( 000 ) 111 ( 000 ) 1.\ \ \ (000)111(000)111(000) 1. (000)111(000)111(000)
2. 111 ( 000 ) 111 ( 000 ) 111 2.\ \ \ 111(000)111(000)111 2. 111(000)111(000)111
-
其中 0 旁边的括号表示:中间的 0 可以省略也符合题意。
-
此时我们可以分情况讨论。
-
第一种情况:也就是我们之前我们求第二弹的代码直接粘过来就行了。主要难在第二种情况
-
第二种情况:我们可以先算出总和,然后减去最小子段和,此时就是最大子段和的值了,怎么实现呢?我们求最小子段和,可以对数组取反,就是正的变成负的,负的变成正的。
AC代码
#include<iostream>
#include<algorithm>
#include<cstring>
//总和-两段最小子段和
using namespace std;
const int N = 2e5+10;
int w[N],n;
int f[N],g[N];
int ans;
int tot;
int sum(){
int ans=-2e9;
for(int i=1;i<=n;i++){
f[i]=max(f[i-1]+w[i],w[i]);
}
for(int i=n;i;i--){
g[i]=max(g[i+1]+w[i],w[i]);
}
for(int i=1;i<=n;i++){
f[i]=max(f[i],f[i-1]);
}
for(int i=n;i;i--){
g[i]=max(g[i+1],g[i]);
}
for(int i=2;i<=n;i++){
ans=max(ans,f[i-1]+g[i]);
}
return ans;
}
int main(){
cin>>n;
memset(f,-0x3f,sizeof f);
memset(g,-0x3f,sizeof g);
for(int i=1;i<=n;i++){
cin>>w[i];
tot+=w[i];
}
ans=sum();/第一种情况
//第二种情况
for(int i=1;i<=n;i++){
w[i]=-w[i];
}
//求最小子段和
if(tot+sum())ans=max(ans,tot+sum());
cout<<ans;
return 0;
}