最大子序和(单调队列优化)

输入一个长度为n的整数序列,从中找出一段不超过M的连续子序列,使得整个序列的和最大。

例如 1,-3,5,1,-2,3


当m=4时,S=5+1-2+3=7

当m=2或m=3时,S=5+1=6

input

第一行:n,m

第二行:n个数

output

最大的和sum


题目分析:首先我们知道O(n)可以很简单的解决原题(不限制子序列的长度)。但是这题加了限制(长度不超过m),并且数据为300000,并不能用O(n*m)的暴力计算最大前缀和之差的方法。所以要用到一些特殊的姿势~~~单调队列。

首先我们分析知道 f[i]=max(sum[i]-s[i-k]),k∈[1,m]。但是一般的维护只能是O(n*m)。变成f[i]=sum[i]-min(sum[i-k]),k∈[1,m]。我们只需要维护一个宽度为m的范围内的sum值的队列,然后每次用最小的去更新f[i],再将sum[i]也加入队列。用优先队列的思想,每个sum最多只能进一次队列出一次队列,所以复杂度是O(2*n)。

#include<iostream>
#include <bits/stdc++.h>
using namespace std;
int a[300100];
long long s[300100],f[300100],x[300100],w[300100];
int main()
{
    int i,n,m,l,r;
    long long ans=0;
    scanf("%d%d", &n, &m);
    memset(f,0,sizeof(f));
    memset(s,0,sizeof(s));
    for (i=1;i<=n;i++) {
        scanf("%d", &a[i]);s[i]=s[i-1]+a[i];
    }
    l=1;r=1;x[1]=0;w[1]=0;
    for (i=1;i<=n;i++) {
        while (w[l]<i-m) l++;
        f[i]=s[i]-s[w[l]];
        while (s[i]<x[r]&&r>=l) r--;
        r++;
        x[r]=s[i];w[r]=i;
    }
    for (i=1;i<=n;i++)
    if (f[i]>ans) ans=f[i];
    printf("%I64d\n", ans);
    return 0;
}

你可能感兴趣的:(最大子序和(单调队列优化))