zoukankan      html  css  js  c++  java
  • 单调队列总结

    定义

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

    入门题(洛谷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("
    ");
        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("
    ");
        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
    ",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
    ",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
    ");
        else printf("%d
    ",l);
        return 0;
    }
    

    其它题目

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

  • 相关阅读:
    OSI安全体系结构
    PHP 二维数组根据相同的值进行合并
    Java实现 LeetCode 17 电话号码的字母组合
    Java实现 LeetCode 16 最接近的三数之和
    Java实现 LeetCode 16 最接近的三数之和
    Java实现 LeetCode 16 最接近的三数之和
    Java实现 LeetCode 15 三数之和
    Java实现 LeetCode 15 三数之和
    Java实现 LeetCode 15 三数之和
    Java实现 LeetCode 14 最长公共前缀
  • 原文地址:https://www.cnblogs.com/liuchanglc/p/13406472.html
Copyright © 2011-2022 走看看