题目描述
输入一个长度为n的整数序列,从中找出一段不超过M的连续子序列,使得整个序列的和最大。
例如 1,-3,5,1,-2,3
当m=4时,S=5+1-2+3=7
当m=2或m=3时,S=5+1=6
输入格式
第一行两个数n,m
第二行有n个数,要求在n个数找到最大子序和
输出格式
一个数,数出他们的最大子序和
输入样例 #1
6 4
1 -3 5 1 -2 3
输出样例 #1
7
前缀和+单调队列
问题区间 [ i , j ] [i,j] [i,j] 的中子序和可以根据前缀和得出: s u m = a r r [ j ] − a r r [ i − 1 ] sum=arr[j]-arr[i-1] sum=arr[j]−arr[i−1]
所以将问题转化为寻找两个位置 i , j i,j i,j 使得区间 [ i , j ] [i,j] [i,j] 内子序和最大,并且 j − i ≤ M j-i \le M j−i≤M
我们通过枚举右端点 j j j 得到答案,当 j j j 固定时,问题又转化为寻找使 a r r [ i ] arr[i] arr[i] 最小的 i i i ,并且 i ∈ [ j − M , j − 1 ] i \in [j-M,j-1] i∈[j−M,j−1]
可以用单调队列 q q q 来保存这些 位置 ,思想是在队列中及时排除一定不是最优解的元素
我们定义队首为 l l l ,队尾为 r r r ,对右端点 i i i 操作(先进先出 l l l 先进, r r r 先出),队首元素中保存着最优位置
1、判断队首元素 p [ l ] p[l] p[l] 与 i i i 的距离是否大于 M,大于将队首元素出队
2、更新答案 a n s = m a x ( a n s , a r r [ i ] − a r r [ q [ l ] ] ) ans = max(ans, arr[i] - arr[q[l]]) ans=max(ans,arr[i]−arr[q[l]])
3、判断当前元素与队尾元素的前缀和大小,此时 i i i 是一定比 q [ r ] q[r] q[r] 大的,因为后进的嘛,如果 a r r [ q [ r ] ] > = a r r [ i ] arr[q[r]]>=arr[i] arr[q[r]]>=arr[i] 说明 a r r [ q [ r ] ] arr[q[r]] arr[q[r]] 已经不可能是最优解了,轮不到 a r r [ q [ r ] ] arr[q[r]] arr[q[r]] 做贡献了,将其出队
在本题的单调队列中元素是递增的 q [ l ] < q [ r ] q[l]<q[r] q[l]<q[r] ,当位置 q [ l ] q[l] q[l] 因为超过M而不合法时,下一个最小的前缀和出来做贡献,以此类推
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
static const auto io_sync_off = []() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
return nullptr;
}();
const int maxn = 300005;
int arr[maxn], q[maxn];
int main()
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; ++i)
{
cin >> arr[i];
arr[i] += arr[i - 1]; //前缀和
}
q[1] = 0;
int l = 1, r = 1, ans = 0;
for (int i = 1; i <= n; ++i)
{
while (l <= r && q[l] < i - m)
++l; //把个数超过m的出队
ans = max(ans, arr[i] - arr[q[l]]);
while (l <= r && arr[q[r]] >= arr[i])
--r; //把一定不是最优解的出队
q[++r] = i;
}
cout << ans;
return 0;
}