也许更好的阅读体验
D e s c r i p t i o n \mathcal{Description} Description
给出一段环状序列,即认为 a 1 a_1 a1和 a n a_n an是相邻的,选出其中连续不重叠且非空的两段使得这两段和最大。
S o l u t i o n \mathcal{Solution} Solution
思路
要求两个最大子段,考虑其与最大子段的关系
我们先把序列复制一遍,求出长度小于等于 n n n的最大子段
把其左端点作为起点,现在得到一个长度为 n n n的子段
如图,这个最大子段的和一定是正数,旁边一定是负数
证明,如果旁边不是负数那么其不是最大子段
那么要再弄一个子段出来,有且仅有 两种方式
为什么没有另一种方式将包含了最大子段中的某一段并与不包含最大子段的某段凑成两段
既然题目要子段最大,那没道理只取最大子段中的一小段
没有另外的选择方式了 (选一堆负数除外)
实现方法
于是我们要解决的问题就只有两个了
另外,这仅是考虑两个最大子段都是正数的情况,若给出的答案为负数,计算结果就会为0
对这种情况特判一下就好了
还有!
若右边全是负数并且最大子段没有负数,那么我们将最大子段人为的认为是两个子段就好了
说这么多其实特判的并没有什么东西
C o d e \mathcal{Code} Code
/*******************************
Author:Morning_Glory
LANG:C++
Created Time:2019年07月26日 星期五 10时32分59秒
*******************************/
#include
#include
#include
#include
#define ll long long
#define mp make_pair
using namespace std;
const int maxn = 400005;
//{{{cin 读入优化
struct IO{
template<typename T>
IO & operator>>(T&res){
res=0;
bool flag=false;
char ch;
while((ch=getchar())>'9'||ch<'0') flag|=ch=='-';
while(ch>='0'&&ch<='9') res=(res<<1)+(res<<3)+(ch^'0'),ch=getchar();
if (flag) res=~res+1;
return *this;
}
}cin;
//}}}
int n,l1,l2,r1,r2,t,tt;
ll a[maxn];
ll ans,t1,t2;
//{{{_find 找l~r中的最大子段 该子段左端点为lt右端点为rt
ll _find (ll l,ll r,int <,int &rt)
{
deque< pair<ll,ll> > q;//单调队列记得开long long,没开WA了,考试就炸了
ll sum=0,mx=-97865432112345678,res;
q.push_front(mp(l-1,0));
for (int i=l;i<=r;++i){
sum+=a[i];
while (!q.empty()&&i-q.front().first>n) q.pop_front();
if (!q.empty()){
res=sum-q.front().second;
if (res>mx){ lt=q.front().first+1,rt=i,mx=res;}
}
while (!q.empty()&&sum<=q.back().second) q.pop_back();
q.push_back(mp(i,sum));
}
return mx;
}
//}}}
int main()
{
cin>>n;
for (int i=1;i<=n;++i) cin>>a[i],a[i+n]=a[i];
ans=_find(1,2*n,l1,r1);//全局最大子段
if (ans<0){
sort(a+1,a+n+1);
printf("%lld\n",a[n]+a[n-1]);
return 0;
}
t1=_find(r1+1,l1+n-1,l2,r2);//右边的最大子段
for (int i=l1;i<=r1;++i) a[i]=-a[i];
t2=_find(l1,r1,t,tt);//中间的最小子段和的绝对值
ans=max(0ll,max(t1,t2))+ans;//0是对应最后那个还有
printf("%lld\n",ans);
return 0;
}
如有哪里讲得不是很明白或是有错误,欢迎指正
如您喜欢的话不妨点个赞收藏一下吧