长度不超过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
#include
#include
#include
#include
#define LL long long

using namespace std;

LL sum[1000010];

list 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;
}




你可能感兴趣的:(算法小讲堂,STL,弱鸡的DP之路)