题目描述:
输入一个长度为n的整数序列,从中找出一段长度不超过m的连续子序列,使得子序列中所有数的和最大。
输入格式
第一行输入两个整数n,m。
第二行输入n个数,代表长度为n的整数序列。
同一行数之间用空格隔开。
输出格式
输出一个整数,代表该序列的最大子序和。
数据范围
1≤n,m≤300000 1≤n,m≤300000
输入样例:
6 4
1 -3 5 1 -2 3
输出样例:
7
分析:
本题要求输出最大子序列和,与一般的最大连续子序列和不同的是,本题有着长度不超过m的限制,于是就完全不能使用一般最大连续子序列和问题的动态规划来解决了。我们知道,对于数组an而言,其前缀和数组为sn,根据前缀和的性质,an数组里aj+1到ai连续一段的子序列和为si - sj。于是本问题等价于求max{si - sj},其中i - j <= m,且i和j都在0-n的范围内。我们只需遍历每个si,求得以ai为结尾的,长度不超过m的子序列中和最大的,然后其中的最大者就是所求的答案。对于某个固定的si,要想si - sj最大,当且仅当sj最小,于是问题就转化为了求si前m个数中sj最小的是哪个。或者说,等价于求滑动窗口长度为m中每个窗口的最小值,本题就与AcWing 79 滑动窗口的最大值类似了,不同的是,本题是求滑动窗口的最小值,而且窗口的大小并非是完全等于m,因为在求m个数的和时,需要m + 1个前缀和都入队才可以,所以实际窗口的大小为m+1。
具体算法为:先对数组求前缀和,然后将哨兵节点0入队(便于求解第一个元素为最后解的情况),当队列里已有m + 1个元素时,队头出队;当当前元素小于队尾元素时,队尾元素出队,最后将当前元素下标入队,同时看能否更新最大和。
#include <iostream>
#include <algorithm>
#include <deque>
using namespace std;
const int maxn = 300005;
typedef long long ll;
int m,n,a[maxn];
int main(){
cin>>n>>m;
for(int i = 1;i <= n;i++){
cin>>a[i];
a[i] += a[i - 1];
}
deque<int> q;
q.push_back(0);
ll ans = -1e10;
for(int i = 1;i <= n;i++){
while(q.size() && i - q.front() > m) q.pop_front();
ans = max(ans,(a[i] - a[q.front()]) * 1ll);
while(q.size() && a[i] <= a[q.back()]) q.pop_back();
q.push_back(i);
}
cout<<ans<<endl;
return 0;
}