【优化】单调队列与dp

笔者大概看了一下单调队列对于DP的优化,故撰此文,望有帮助。

(dp还是推式子难啊qwq)

例题1.

题目大意:在n个数的序列中,选择数字,使得其连续不超过k个数,且和最大。

本题的方程相对好推:设dp[i][0/1]为到了第i个数,且第i个数不选/选的最大值。

则有转移:dp[i][0]=max(dp[i-1][0],dp[i-1][1])

dp[i][1]=max{dp[j][0]-sum[j]+sum[i]},i-k<=j<i

枚举j即可。

但是题目会这么让你水过吗?

发现会超时,优化不可避免。

仔细观察方程,考虑它的特殊性。

对于方程1,我们不便再多做什么。但是对于方程2,显然仍有优化余地。

我们将sum[i]提出来,得到:

dp[i][1]=max{dp[j][0]-sum[j]}+sum[i].

这个方程只与j有关,我们让max里面的最大就好了。

维护它,我们可以用堆,也可以用单调队列,线段树。

本文主要讲对于单调队列的优化。它可以保证O(n)的时间复杂度。

首先,我们明确一点,我们维护的队首元素最大。显然,队列中的数字要单调递减。

其次,我们要严格确保我们查询的区间,保证队列中没有没用的数字。

并且每次枚举到下一个数的时候,注意更新队列。

考虑何时更新更优:

首先,当这个数字不在需要的范围的时候,删除即可。

其次,对于新插入的数字,我们要从队尾插入,比较哪个值更优:

判断它们的“浪费情况”即可。

用q[]表示队列,s[]表示前缀和,则判断:

s[q[head]]-f[q[head]][0]>s[i]-f[i][0]&&head<=tail

如果符合的话,就把它删掉吧。因为队尾的元素所浪费的比新插入的值多,显然一定不如它优。

由此,我们已经保证了单调队列的稳定复杂度。

给出代码:

#include<cstdio>
#include<iostream>
using namespace std;
long long q[2000000],f[2000000][2];
long long n,k,s[2000000],a[2000000];
long long tail,head;
int main(){
    scanf("%lld%lld",&n,&k);
    for(int i=1;i<=n;++i){
        scanf("%lld",&a[i]);
        s[i]=s[i-1]+a[i];//sum
    }
    tail=head=1;//初始化 
    for(int i=1;i<=n;++i){
        f[i][0]=max(f[i-1][0],f[i-1][1]);//对于不选i,只考虑前面两个即可 
        while(q[head]<i-k&&head<=tail)head++;//判断队头是否在所找区间内 
        f[i][1]=f[q[head]][0]-s[q[head]]+s[i];//取MAX转移 
        while(f[i][0]-s[i]>f[q[tail]][0]-s[q[tail]]&&head<=tail)tail--;
        q[++tail]=i;//更新队尾,当队列有数且当前队尾若插入i不满足单调性时
        //写成s[i]-f[i][0]<s[q[tail]]-f[q[tail]][0]也可以
        //可以理解为选到i和队尾时,两者不选的奶牛的效率和相比较,显然浪费少的更优,不优的删除即可 
    }printf("%lld\n",max(f[n][0],f[n][1]));
    return 0;
}

双倍经验:P2034

持续更新中。

猜你喜欢

转载自www.cnblogs.com/h-lka/p/11220841.html
今日推荐