【单调队列】max

最大子序列的和 (max.c/cpp/pas)

【题目描述】

LazyChild有一个长度为N的整数序列(a1,a2,…,an),他希望你从中找出一段连续的长度不小于A,且不超过B的子序列,使得这个子序列的和最大。

例如:1,-3,5,1,-2,3。

当A=2,B=2或3时  S=5+1=6。当A=3,B=4时  S=5+1+(-2)+3=7

【输入文件】

第一行三个整数N,A,B(1<=A<=B<=N)。

第二行为N整数,每个整数用空格隔开,表示该整数序列。

【输出文件】

一行一个整数,为最大子序和。

【样例输入】

6 3 4

1 -3 5 1 -2 3

【样例输出】

7

【数据规模和约定】

对于30%的数据,N<=1000

对于另外30%的数据, A = 1且 B = n。

对于100%的数据,N<=500000。


这道题明显是单调队列,只是一开始我的思路不够严谨,弄出一个有反例的单调队列,WA80。


一开始我的办法是从1到n枚举,单调队列的维护和枚举是同步的,更新ans的条件是i和队首元素位置差值大于等于a,而当大于b时,左指针向右移。

但是这样就有反例。假如全部为负数,则从左到右,sum一直减小,因此存在于队列当中的,一定永远只有sum[i]。

因此永远距离都为0而不能更新答案。


正确的方法应该是单调队列和枚举异步进行,即手动使单调队列的元素队尾元素位置和i相差a,使得所有元素都和i相差大于等于a,这样就不存在反例了。


#include <cstdio>
#include <cstring>
#include <iostream>
#include <string>
using std::cout;
#define max(a,b) ((a)>(b)?(a):(b))

typedef long long ll;

long num[500010];
ll sum[500010];
long que[500010];
ll ans = -0x3f3f3f3f3f3f3f3fll;

long getint()
{
	long rs=0;bool sgn=1;char tmp;
	do tmp = getchar();
	while (!isdigit(tmp)&&tmp-'-');
	if (tmp == '-'){tmp=getchar();sgn=0;}
	do rs=(rs<<3)+(rs<<1)+tmp-'0';
	while (isdigit(tmp=getchar()));
	return sgn?rs:-rs;	
}

int main()
{
	freopen("max.in","r",stdin);
	freopen("max.out","w",stdout);
	long n = getint();
	long a = getint();
	long b = getint();
	for (long i=1;i<n+1;i++)
	{
		num[i] = getint();
		sum[i] = sum[i-1]+num[i];
	}

	long l = 0;
	long r = 0;
	
	r ++;
	que[r] = 0;
	for (long i=a;i<n+1;i++)
	{
		while (l<r && i-que[l+1]>b) l++;
		while (l<r && sum[que[r]]>=sum[i-a]) r--;
		r ++;
		que[r] = i-a;
		ans = max(ans,sum[i]-sum[que[l+1]]);
	}

	cout << ans;
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值