最大子序和
输入一个长度为 n n n 的整数序列,从中找出一段长度不超过 m m m 的连续子序列,使得子序列中所有数的和最大。
注意: 子序列的长度至少是 1 1 1。
输入格式
第一行输入两个整数
n
,
m
n,m
n,m。
第二行输入 n n n 个数,代表长度为 n n n 的整数序列。
同一行数之间用空格隔开。
输出格式
输出一个整数,代表该序列的最大子序和。
数据范围
1
≤
n
,
m
≤
300000
1≤n,m≤300000
1≤n,m≤300000
输入样例:
6
4 1 -3 5 1 -2 3
输出样例:
7
算法:单调队列 O ( n ) O(n) O(n)
- 计算区间和,容易想到前缀和,可以在 O ( 1 ) O(1) O(1)的时间内得到子序列的和
- 枚举以i结尾的子序列,往前延伸最多
m
个单位找到s[j]
的最小值,有前缀和得到子序列和公式: sum=s[i]-s[j-1]=a[j]+a[j+1]+...+a[i]
- 在枚举以
i
为结尾的子序列时,i
是确定的,即s[i]
为定值,我们只需要在不超过m个单位的连续长度里找到最小的s[j]
,相减即为最大值 - 假设单调队列元素为
0 4 2
,那么2
一定是比4
更好的s[j]
,而且4
一定在2
前面出列,因此我们可以直接淘汰,更新队列时从队尾遍历到队头找到插入的位置即可,大于或等于s[i]
的队尾元素可以一直淘汰
C++ 代码
#include <iostream>
using namespace std;
const int N=300010;
long long s[N];
int n,m;
int q[N],hh,tt;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
scanf("%lld",&s[i]);
s[i]+=s[i-1];
}
//q[0]=0,默认以0作为前导方便计算
long long res=-0x3f3f3f3f;
for(int i=1;i<=n;i++)//枚举以i结尾的连续子序列
{
if(i-q[hh]>m) hh++;//维护窗口大小
res=max(res,s[i]-s[q[hh]]);//O(1)的时间得到以i结尾的不超过m的连续子序列最大值
while(hh<=tt&&s[q[tt]]>=s[i]) tt--;
q[++tt]=i;//找到符合递增的位置入队
}
cout<<res<<endl;
return 0;
}
题目来源:AcWing