长度不超过m的最大连续子序列(dp + 单调队列)

给你n个数,然后让你求最大连续子序列的和,限制条件是连续子序列的长度不超过m。

n,m  <= 100000;

就是让你找到一个长度不超过m的区间,区间和最大。

普通的dp转移方程就是 dp[i] = sum[i] - min(sum[j] | i- m <= j <= i) 

但是这样的复杂度最坏会达到n^2,所以得优化,就用到了单调队列。

针对这题来说一下什么是单调队列,这题我们需要存下距离不超过m,且最小的前缀和的下标。

如果来了一个前缀,肯定下标是比在队列里的是靠后的,如果它的值还比队列里的小,那么队列里的元素就没有必要存在了,就把它们踢出去。


举个例子,假设右边是队列的首,左边是队列的尾,现在队列里的元素的值(注意这里不是下标,下标靠近队首的较小)为 {5 4 -2},现在又来了一个值为1的数,那么它要和队尾的元素对应的值进行比较,发现比5,4都小,所以5,4都被弹出队列,比到-2的时候没有-2小,所以插入队列。

这样的话最小值就是队首的元素,当然在用队首元素的时候还要在看一下当前的队首元素还符不符合限制条件,如果不符合就弹出。

因为每个元素最多只会进一次队列,出一次队列,所以复杂度就是O(n)。


这里用了链表来模拟队列,就可以方便的对队首队尾元素进行操作。

#include<list>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define LL long long

using namespace std;

LL sum[1000010];

list<int> q;

int main(void)
{
    int n,m,i,j;
    while(scanf("%d%d",&n,&m)==2)
    {
        sum[0] = 0;
        for(i=1;i<=n;i++)
        {
            scanf("%lld",&sum[i]);
            sum[i] += sum[i-1];
        }
        LL maxn = 0;
        while(!q.empty())
            q.pop_back();
        q.push_front(0);
        for(i=1;i<=n;i++)
        {
            while(!q.empty() && sum[q.front()] > sum[i])
            {
                q.pop_front();
            }
            q.push_front(i);
            while(!q.empty() && i - q.back() > m)//如果长度已经超过m
            {
                q.pop_back();
            }
            maxn = max(maxn,sum[i] - sum[q.back()]);
        }
        cout << maxn << endl;
    }

    return 0;
}




  • 5
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值