最大子序列和
输入一个长度为 n 的整数序列,序列中可能有负数,从中找出一段长度不超过 m的连续子序列,使得子序列中所有数的和最大。
说明: 子序列的长度至少是 1。
输入格式
第一行输入两个整数 n,m。
第二行输入 n个数,代表长度为 n的整数序列。
同一行数之间用空格隔开。
输出格式
输出一个整数,代表该序列的最大子序和。
数据范围
1≤n,m≤300000
输入样例:
8 5
2 -1000 -500 2 8 -500 -700 0
输出样例:
10
解析
此题数据范围偏大,暴力枚举不合适。
用到的知识点:区间和;前缀和相减; 单调队列
原问题转化为: 找出两个位置x,y, 使S[y] - S[x] 最大,且 y - x <= m ,S是前缀和数组。
可以枚举右侧端点 i, 当 i 固定时,问题变为: 找到一个左端点 j,S[j]最小,j 属于[i - m, i - 1] 。
比较任意两个位置 j 和 k , 如果 k < j < i 且 S[k] >= S[j] ,那么对于所有大于等于 i 的右端点,k 永远不会成为最优选择。
因为不仅 S[k] 大于等于 S[j] ,而且 j 离 i 更近,长度更不容易超过 m,显然 j 的生存能力比 k 更强。所以当 j 出现(进队)后, k 就完全是一个无用的位置下标。
以上分析说明,可能成为最优选择的策略集合一定是: 下标位置严格递增,对应的前缀和S 的值也严格递增 的序列。可以用队列保存这个序列,m >= 序列长度 >= 1 。
单调队列算法时间复杂度大致为O(N) : 每个位置的元素最多入队一次,出队一次。
#include <iostream>
#include <limits.h>
using namespace std;
const int N = 300010;
typedef long long ll;
ll res = INT_MIN;
int n, m;// m is window
int q[N];
ll s[N];
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i ++)
{
cin >> s[i];
s[i] += s[i -1];
}
int hh = 0, tt = 0;
for(int i = 1; i <= n; i ++)
{
if(i - q[hh] > m) hh ++;//out of date
res = max(res,s[i] - s[q[hh]]);
while(hh <= tt && s[q[tt]] >= s[i]) tt --;
q[++tt] = i;
}
cout << res << endl;
return 0;
}