.输入一个长度为 n 的整数序列,从中找出一段长度不超过 m 的连续子序列,使得子序列中所有数的和最大。
注意: 子序列的长度至少是 1。
输入格式
第一行输入两个整数 n,m。
第二行输入 n 个数,代表长度为 n 的整数序列。
同一行数之间用空格隔开。
输出格式
输出一个整数,代表该序列的最大子序和。
数据范围
1≤n,m≤300000
输入样例:
6 4
1 -3 5 1 -2 3
输出样例:
7
思路
对于固定m的可以直接用前缀和枚举求解, 而这里是不超过m, 故需要考虑更多情况。
先找到暴力枚举的顺序, 然后再进行优化。
这里可以枚举每个子序列的节点, 然后往前枚举所有长度不超过m的子序列, 利用前缀和来计算区间和。
根据计算公式 s[i] - s[j - 1]
, 显然想要求最大, 肯定要让s[i]
越大, s[j - 1]
越小越好。
而往前枚举所有长度不超过m的子序列时, 也就是枚举 s[j - 1]
, 即 s[i]
是确定, 那么问题就转化为:找一个区间内的最小值s[j - 1]
。
可以使用单调队列优化。
res 在q入队之前更新是因为, 需要是不包含i的最小值, 不然i有可能影响到, 最后变成
同理, 这里队头出队的边界为 的区间长度包含 为实际数量减一,如果按正常的单调队列, 这里应该用>= 表示超过, 或者用, 但因为求得是前缀和的, 故取值范围在 , 其长度为, 加上的总长度为, 即, 优化一下为
代码
#include <iostream>
using namespace std;
const int N =3e5 + 10;
int n,m;
int hh,tt;
int q[N];
int a[N];
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i++)
{
cin >> a[i];
a[i] += a[i - 1];
}
int res = -2e9;
for(int i = 1; i <= n; i++)
{
while(q[hh] < i - m) hh++;
res = max(res, a[i] - a[q[hh]]);
while(hh <= tt && a[i] <= a[q[tt]]) tt--;
q[++tt] = i;
}
cout << res << endl;
return 0;
}