最大子序列的和 (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;
}