单调队列总结

定义

单调队列,即单调递减或单调递增的队列。

入门题(洛谷P1886滑动窗口)

传送门

题目描述

分析

如果用暴力求解的话,我们要将这一个长度为\(k\)的区间扫一遍
但是实际上,有很多值是显然不会对答案产生贡献的
比如我们要维护该区间的最大值,当前队尾的的元素是\(4\),下一个要加进去的元素是\(5\)
此时队尾一定不会对答案产生贡献,因为它的值比下一个元素小,而且当前值继续对答案产生贡献的时间也更短
这样,我们就相当于维护了一个单调递减的队列
维护区间最小值同理

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
typedef long long ll;
ll a[maxn];
int q[maxn];
int main(){
    int n,k;
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++){
        scanf("%lld",&a[i]);
    }
    int head=1,tail=0;
    for(int i=1;i<=n;i++){
        while(head<=tail && i-q[head]+1>k) head++;
        while(head<=tail && a[i]<a[q[tail]]) tail--;
        q[++tail]=i;
        if(i>=k)printf("%lld ",a[q[head]]);
    }
    printf("\n");
    head=1,tail=0;
    for(int i=1;i<=n;i++){
        while(head<=tail && i-q[head]+1>k) head++;
        while(head<=tail && a[i]>a[q[tail]]) tail--;
        q[++tail]=i;
        if(i>=k)printf("%lld ",a[q[head]]);
    }
    printf("\n");
    return 0;
}

其它题目

P2952 [USACO09OPEN]Cow Line S
P1440 求m区间内的最小值
P1638 逛画展
P1901 发射站
P2032 扫描
P2947 [USACO09MAR]Look Up S
P1714 切蛋糕
P2629 好消息,坏消息

单调队列优化DP

P2627 [USACO11OPEN]Mowing the Lawn G

题目描述

分析

暴力的\(DP\)方程比较好想,我们设\(f[i]\)为选择到第\(i\)头奶牛,并且第\(i\)头奶牛必须选的最大价值
\(f[i]=max(f[i],f[j]+sum[i]-sum[j+1])(i-(j+1)<=k)\)
我们只需要用单调队列搞一下\(f[j]-sum[j+1]\)的最大值即可

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
typedef long long ll;
ll f[maxn],head,tail,sum[maxn],a[maxn];
int q[maxn];
int main(){
    int n,k;
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++){
        scanf("%lld",&a[i]);
    }
    for(int i=1;i<=n;i++){
        sum[i]=sum[i-1]+a[i];
        if(i<=k) f[i]=sum[i];
    }
    ll ans=0;
    head=1,tail=1;
    for(int i=1;i<=n;i++){
        while(head<=tail && i-q[head]-1>k) head++;
        f[i]=max(f[i],f[q[head]]+sum[i]-sum[q[head]+1]);
        ans=max(ans,f[i]);
        while(head<=tail && f[i]-sum[i+1]>f[q[tail]]-sum[q[tail]+1]) tail--;
        q[++tail]=i;
    }
    printf("%lld\n",ans);
    return 0;
}

P1725 琪露诺

题目描述

分析

对于每一个给定的\(i\),我们都可以从\([i-r,i-l]\)区间中选出一个最大的\(f\)值来更新
用一个单调递减的队列维护即可
注意队列中存的是位置,而不是标号

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+5;
int a[maxn],q[maxn],f[maxn];
int main(){
    for(int i=0;i<maxn;i++){
        f[i]=-0x3f3f3f3f;
    }
    int n,l,r;
    scanf("%d%d%d",&n,&l,&r);
    for(int i=0;i<=n;i++){
        scanf("%d",&a[i]);
    }
    f[0]=0;
    int head=1,tail=0,cnt=0;
    for(int i=l;i<=n;i++){
        while(head<=tail && f[q[tail]]<=f[i-l]) tail--;
        q[++tail]=i-l;
        while(q[head]+r<i) head++;
        f[i]=f[q[head]]+a[i];
    }
    int ans=-0x3f3f3f3f;
    for(int i=n-r+1;i<=n;i++){
        ans=max(ans,f[i]);
    }
    printf("%d\n",ans);
    return 0;
}

P3957 跳房子

题目描述

分析

这道题刚一看上去和上一道题一模一样,但是队列里面存储的不是位置而是标号
因此我们要用双重循环来维护

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=5e5+5;
int n,d,k;
int f[maxn],q[maxn],a[maxn],wz[maxn];
bool jud(int dd){
    for(int i=0;i<maxn;i++) f[i]=-0x3f3f3f3f;
    memset(q,0,sizeof(q));
    int tmin=max(1,d-dd),tmax=d+dd;
    int ans=0;
    int head=1,tail=0;
    f[0]=0;
    for(int i=1,j=0;i<=n;i++){
        while(wz[i]-wz[j]>=tmin && j<i){
            if(f[j]!=-0x3f3f3f3f){
                while(head<=tail && f[q[tail]]<=f[j]) tail--;
                q[++tail]=j;
            }
            j++;
        }
        while(head<=tail && wz[i]-wz[q[head]]>tmax) head++;
        if(head<=tail) f[i]=f[q[head]]+a[i];
        ans=max(ans,f[i]);
    }
    if(ans>=k) return 1;
    return 0;
}
int main(){
    scanf("%d%d%d",&n,&d,&k);
    for(int i=1;i<=n;i++){
        scanf("%d%d",&wz[i],&a[i]);
    }
    int l=0,r=1e9,mids;
    while(l<=r){
        mids=(l+r)>>1;
        if(jud(mids)) r=mids-1;
        else l=mids+1;
    }
    if(l>1e9) printf("-1\n");
    else printf("%d\n",l);
    return 0;
}

其它题目

P2422 良好的感觉
P3572 [POI2014]PTA-Little Bird
P3800 Power收集
P3594 [POI2015]WIL-Wilcze doły

posted @ 2020-07-30 21:35  liuchanglc  阅读(225)  评论(0编辑  收藏  举报