[洛谷]P1121 环状最大两段子段和

也许更好的阅读体验
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的子段
如图,这个最大子段的和一定是正数,旁边一定是负数

证明,如果旁边不是负数那么其不是最大子段

那么要再弄一个子段出来,有且仅有 两种方式

  • r + x <mtext>   </mtext> <mtext>   </mtext> i + n 1 r+x\ ~ \ i+n-1 r+x  i+n1中的最大段取出来与最大段凑成两段
  • 最大段可能是有不连续的正数一起拼出来的,也就是说中间有负数
    • 如图
    • 另一种方式就是将最大子段拆成两个子段

为什么没有另一种方式将包含了最大子段中的某一段并与不包含最大子段的某段凑成两段
既然题目要子段最大,那没道理只取最大子段中的一小段

没有另外的选择方式了 (选一堆负数除外)


实现方法

于是我们要解决的问题就只有两个了

  • 怎么求长度小于等于 n n n的最大子段
    如果没有长度限制相信大家都会求
    即用 s u m [ i ] sum[i] sum[i]表示前缀和,在前面找一个最小的 s u m [ j ] sum[j] sum[j],用 s u m [ i ] s u m [ j ] sum[i]-sum[j] sum[i]sum[j]就是以 i i i为结尾的最大子段了
    只需 O ( n ) O(n) O(n)扫一遍即可
    有了长度限制的最大子段
    还是要再前面找一个最小的 s u m [ j ] sum[j] sum[j],并且要有 i j &lt; n <mtext>   </mtext> ( i j + 1 n ) i-j&lt;n\ (i-j+1\leq n) ij<n (ij+1n)
    用单调队列维护一下就好了,即此时最小的 s u m [ j ] sum[j] sum[j]不满足长度条件,那我们就换成满足条件的 s u m [ j ] sum[j] sum[j]
  • 怎么计算答案
    先求一遍最大子段
    第一种情况 求出其右端点 r r r l + n 1 l+n-1 l+n1的最大子段
    第二种情况 可认为是减去中间的一个最小子段,把所有数变成其相反数,再求一遍最大子段即可求出要减去的数的绝对值(也就是要加上的数)

另外,这仅是考虑两个最大子段都是正数的情况,若给出的答案为负数,计算结果就会为0
对这种情况特判一下就好了

还有!
若右边全是负数并且最大子段没有负数,那么我们将最大子段人为的认为是两个子段就好了
说这么多其实特判的并没有什么东西


C o d e \mathcal{Code} Code

/******************************* Author:Morning_Glory LANG:C++ Created Time:2019年07月26日 星期五 10时32分59秒 *******************************/
#include <cstdio>
#include <fstream>
#include <queue>
#include <algorithm>
#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 &lt,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;
}

如有哪里讲得不是很明白或是有错误,欢迎指正
如您喜欢的话不妨点个赞收藏一下吧

全部评论

相关推荐

邮小鼠:粤嵌的项目水的要死 来我们学校带过课程实习 项目名字是车机终端 实际上就是写了了个gui 还是老师把代码发给你你改改的那种
点赞 评论 收藏
分享
白菜小丑呜呜:集美,简历有点问题,+v细嗦
点赞 评论 收藏
分享
评论
点赞
收藏
分享
牛客网
牛客企业服务