最大子序列和(单调队列算法)

Posted xxjnoi

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了最大子序列和(单调队列算法)相关的知识,希望对你有一定的参考价值。

题目大意:

给定一个长度为N的序列,请你求出它最大长度不超过M的最大子序列的和(其中 N,M<=3*10^5)

分析:

一般对于这样的题目,我们最现实想到的就是前缀和,通过枚举序列可以得到答案,但这样的时间复杂度显然是不乐观的(TLE)

所以我们可以通过队列来优化  (这个算法我们称之为单调队列算法

我们先枚举子序列的有端点 i 

此时问题转变为寻找一个 j 最为子序列的左端点 (i-m <= j <= i-1),使得是S[ j ] 最小 (S数组表示前缀和)

这时我们不妨再设一个 k ( k < j < i )并且S[ k ] <  S[ j ],那么对于所有i之后的子序列右端点 k 都不会成为最优的选择。因为 S[ j ] < S[ k ],并且 j > k ,j 更容易满足子序列长度小于 m 这一条件并且S[ i ] - S[ j ]得到的子序列和也更大,故当 j 出现后 k 就是一个无用的选择,在之后的计算的可以将其忽略

上述比较告诉我们,成为最优的选择的集合的一定是一个下标递增,其前缀和也递增的序列 

我们便可以用一个队列来实现这个过程(队列中保存的就是可供选择的子序列左端点)

1,判断队首的决策与 i 的距离是否不超过m

2,此时队首的决策就是子序列右端点为 i 的最优选择

3,不断删除队尾的决策直至队尾对应的前缀和小于 i 的前缀和,再把 i 做为一个新的决策入队

代码如下:

#include<stdio.h>
#include<algorithm>
#include<string.h>
using namespace std;
#define N 300005
int a[N];
int sum[N];
int q[N];
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        sum[i]=sum[i-1]+a[i];
    }
    int l=1,r=1;
    int ans=0;
    q[1]=0;
    for(int i=1;i<=n;i++)
    {
        if(l<=r&&q[l]<i-m)l++; //第一步
        ans=max(ans,sum[i]-sum[q[l]]); //第二步
        while(l<=r&&sum[q[r]]>=sum[i])r--; //第三步
        q[++r]=i;
    }
    printf("%d
",ans);
    return 0;
}

 

以上是关于最大子序列和(单调队列算法)的主要内容,如果未能解决你的问题,请参考以下文章

最大子序列和模板 用单调队列

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

tyvj1305(最大子序列和————单调队列)

⭐算法入门⭐《队列 - 单调队列》中等03 —— LeetCode 918. 环形子数组的最大和

最大子序和(数组模拟单调队列)

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