zoukankan      html  css  js  c++  java
  • 单调队列以及单调队列优化DP

    单调队列定义:

      其实单调队列就是一种队列内的元素有单调性的队列,因为其单调性所以经常会被用来维护区间最值或者降低DP的维数已达到降维来减少空间及时间的目的。

      单调队列的一般应用:

        1.维护区间最值

        2.优化DP

    例题引入:

      求m区间内的最小值:https://www.luogu.org/problemnew/show/P1440

      一个含有n项的数列(n<=2000000),求出每一项前的m个数到它这个区间内的最小值。若前面的数不足m项则从第1个数开始,若前面没有数则输出0。

    例题解答:

       首先看到题目可以很快想到O(NM),对于2*10^6这样的数据无疑要TLE的;

       接下来考虑用单调队列,因为每一个答案只与当前下标的前m个有关,所以可以用单调队列维护前m的个最小值,

       考虑如何实现该维护的过程??

       显然当前下标X的m个以前的元素(即下标小于X-M+1的元素)肯定对答案没有贡献,所以可以将其从单调队列中删除。

       对于两个元素A,B,下标分别为a,b,如果有A>=B&&a<b那么B留在队列里肯定优于A,因此可以将A删除。

       维护队首:如果队首已经是当前元素的m个之前,将head++,弹出队首元素

       维护队尾:比较q[tail]与当前元素的大小,若当前元素更优tail++,弹出队尾元素,直到可以满足队列单调性后加入当前元素。

       考虑单调队列的时间复杂度:由于每一个元素只会进队和出队一次,所以为O(N)。

       一般建议用数组模拟单调队列进行操作,而不用系统自带的容器,因为系统自带容器不易调试且可能有爆空间的危险。

     

    代码实现:

      

    #include<bits/stdc++.h>
    using namespace std;
    #define re register int
    #define INF 0x3f3f3f3f
    #define ll long long
    #define maxn 2000009
    #define maxm
    inline ll read()
    {
        ll x=0,f=1;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')    f=-1;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ll)(ch-'0');ch=getchar();}
        return x*f;
    }
    int n,m,k,tot,head,tail;
    int a[maxn],q[maxn];
    int main()
    {
    //    freopen(".in","r",stdin);
    //    freopen(".out","w",stdout);
        n=read(),m=read();
        for(int i=1;i<=n;i++)
            a[i]=read();
        head=1,tail=0;//起始位置为1 因为插入是q[++tail]所以要初始化为0
        for(int i=1;i<=n;i++)//每次队首的元素就是当前的答案 
        {
            printf("%d
    ",a[q[head]]);
            while(i-q[head]+1>m&&head<=tail)//维护队首 
                head++;
            while(a[i]<a[q[tail]]&&head<=tail)//维护队尾 
                tail--;
            q[++tail]=i;
        }
    //    fclose(stdin);
    //    fclose(stdout);
        return 0;
    }

     

    习题报告:

       滑动窗口:https://www.luogu.org/problemnew/show/P1886

       解题思路: 此题与例题相同,只是所要求的是最大值和最小值,只需要做两遍单调队列即可   

    #include<bits/stdc++.h>
    using namespace std;
    #define re register int
    #define ll long long
    #define maxn 1000009
    #define maxm
    inline ll read()
    {
        ll x=0,f=1;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ll)(ch-'0');ch=getchar();}
        return x*f;
    }
    int q[maxn],a[maxn];
    int n,m,k,ans,tot,head,tail; 
    
    void Ask_MIN()
    {
        head=1,tail=0;
        for(int i=1;i<=n;i++)
        {
            while(head<=tail&&i-q[head]+1>m)
                head++;
            while(head<=tail&&a[q[tail]]>=a[i])
                tail--;
            q[++tail]=i;
            if(i>=m)
                printf("%d ",a[q[head]]);    
        }
        puts("");
    }
    
    void Ask_MAX()
    {
        head=1,tail=0;
        for(int i=1;i<=n;i++)
        {
            while(head<=tail&&i-q[head]+1>m)
                head++;
            while(head<=tail&&a[q[tail]]<=a[i])
                tail--;
            q[++tail]=i;
            if(i>=m)
                printf("%d ",a[q[head]]);
        }
        puts("");
    }
    int main()
    {
    //    freopen(".in","r",stdin);
    //    freopen(".out","w",stdout);
        n=read(),m=read();
        for(int i=1;i<=n;i++)
            a[i]=read();
        Ask_MIN();
        Ask_MAX();
        fclose(stdin);
        fclose(stdout);
        return 0;
    }
    View Code

     

     

       挤奶牛:https://www.luogu.org/problemnew/show/P3088

       解题思路:此题题目需要维护左和右分别D区间内的最大值,因此可以正着和倒着分别做一次单调队列,然后打标记即可。

     

    #include<bits/stdc++.h>
    using namespace std;
    #define re register int
    #define ll long long
    #define maxn 50009
    #define maxm
    inline ll read()
    {
        ll x=0,f=1;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ll)(ch-'0');ch=getchar();}
        return x*f;
    }
    struct cow
    {
        int h,x;
    }p[maxn];
    int q[maxn];
    bool fear[maxn];
    int n,m,k,ans,tot,head,tail; 
    bool comp(cow a,cow b)
    {
        return a.x<b.x;
    }
    int main()
    {
    //    freopen(".in","r",stdin);
    //    freopen(".out","w",stdout);
        n=read(),m=read();
        for(int i=1;i<=n;i++)
            p[i].x=read(),p[i].h=read();
        sort(p+1,p+1+n,comp);
        head=1,tail=0;
        for(int i=1;i<=n;i++)
        {
            while(head<=tail&&p[i].x-p[q[head]].x>m)
                head++;
            while(head<=tail&&p[i].h>=p[q[tail]].h)
                tail--;
            q[++tail]=i;
            if(p[q[head]].h>=2*p[i].h)
                fear[i]=1;
        }
        head=1,tail=0;
        for(int i=n;i>=1;i--)
        {
            while(head<=tail&&p[q[head]].x-p[i].x>m)
                head++;
            while(head<=tail&&p[q[tail]].h<=p[i].h)
                tail--;
            q[++tail]=i;
            if(p[q[head]].h>=p[i].h*2&&fear[i])
                ans++;
        }
        printf("%d
    ",ans);
        fclose(stdin);
        fclose(stdout);
        return 0;
    }
    View Code

     

        好消息,坏消息:https://www.luogu.org/problemnew/show/P2629

       解题思路:先断环成链,便于操作,然后就变成求对于每一个合法的K,都要满足k到(n-k+1)中,任意一点的和都是非负的,用前缀和计算区间和,那么只需要满足sum[i]-sum[k-1]>=0(k<=i<=n+k-1),不需要判断每一个点,只需要转换一下变成判断最小的sum[i]减去最大的sum[k-1]是否大于等于0就行了,因为只要有一个点为负数就已经不合法了。

     

    #include<bits/stdc++.h>
    using namespace std;
    #define re register int
    #define ll long long
    #define maxn 1000009
    #define maxm
    inline ll read()
    {
        ll x=0,f=1;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ll)(ch-'0');ch=getchar();}
        return x*f;
    }
    int sum[maxn<<1],q[maxn<<1],a[maxn<<1];
    int n,m,k,ans,tot,head,tail; 
    int main()
    {
    //    freopen(".in","r",stdin);
    //    freopen(".out","w",stdout);
        n=read();
        for(int i=1;i<=n;i++)
            a[i]=read(),a[i+n]=a[i];
        for(int i=1;i<=2*n;i++)
            sum[i]=sum[i-1]+a[i];
        head=1,tail=0;
        for(int i=1;i<=n*2-1;i++)
        {
            while(head<=tail&&i-q[head]+1>n)
                head++;
            while(head<=tail&&sum[i]<=sum[q[tail]])
                tail--;
            q[++tail]=i;
            if(i>=n&&sum[q[head]]-sum[i-n]>=0)
                ans++;
        }    
        printf("%d
    ",ans);
        fclose(stdin);
        fclose(stdout);
        return 0;
    }
    View Code

         

      

    单调队列优化DP:

      大部分单调队列优化的DP都和定长连续子区间的最值问题有关。

      单调队列一般优化线性DP,形如dp[i]=max/min(dp[j])+val[i],且j<i,val[i]与dp[j]无关,此时优化的对象是dp[j]。

      一般来说i,j是需要通过两层嵌套循环来实现枚举,但是因为dp[j]与val[i]无关,所以我们可以维护一下已经计算好了的dp[j],使其不需要用for循环来枚举。

    例题引入:

         最大连续和:https://loj.ac/problem/10176

        给你一个长度为 n 的整数序列,要求从中找出一段连续的长度不超过 m 的子序列,使得这个序列的和最大。

     

    例题解答:

      首先考虑DP方程:用dp[i]表示以i为结尾的长度不超过m的最大子序列和。

       转移为:dp[i]=max{sum[i]-sum[i-k],k=1.2....m}

                =sum[i]-min{sum[i-k],k=1.2....m};

       最后转变为对于所有的1<=k<=m,找出所有sum[i-k]的最小值。

       考虑用单调队列来维护决策值sum[i-k]就行啦。

         

    代码实现:

      

    #include<bits/stdc++.h>
    using namespace std;
    #define re register int
    #define ll long long
    #define maxn 200009
    #define maxm
    inline ll read()
    {
        ll x=0,f=1;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ll)(ch-'0');ch=getchar();}
        return x*f;
    }
    int sum[maxn],q[maxn];
    int n,m,k,ans,tot,mx,head,tail; 
    int main()
    {
    //    freopen(".in","r",stdin);
    //    freopen(".out","w",stdout);
        n=read(),m=read();
        for(int i=1;i<=n;i++)
        {
            int x=read();
            sum[i]=sum[i-1]+x;
        }
        ans=-0xffffff;
        head=1,tail=1;
        for(int i=1;i<=n;i++)
        {
            while(head<=tail&&i-q[head]>m)
                head++;    
            ans=max(ans,sum[i]-sum[q[head]]);
            while(head<=tail&&sum[q[tail]]>=sum[i])
                tail--;
            q[++tail]=i;    
        }
        printf("%d
    ",ans);
        fclose(stdin);
        fclose(stdout);
        return 0;
    }

       

     

    习题报告:

     

      切蛋糕:https://www.luogu.org/problemnew/show/P1714

       解题思路:和例题一样。

      

    #include<bits/stdc++.h>
    using namespace std;
    #define re register int
    #define ll long long
    #define maxn 500009
    #define maxm
    inline ll read()
    {
        ll x=0,f=1;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ll)(ch-'0');ch=getchar();}
        return x*f;
    }
    int sum[maxn],q[maxn];
    int n,m,k,ans,tot,mx,head,tail; 
    int main()
    {
    //    freopen(".in","r",stdin);
    //    freopen(".out","w",stdout);
        n=read(),m=read();
        for(int i=1;i<=n;i++)
        {
            int x=read();
            sum[i]=sum[i-1]+x;
        }
        head=1,tail=0;
        for(int i=1;i<=n;i++)
        {
            while(head<=tail&&i-q[head]>m)
                head++;    
            ans=max(ans,sum[i]-sum[q[head]]);
            while(head<=tail&&sum[q[tail]]>=sum[i])
                tail--;
            q[++tail]=i;    
        }
        printf("%d
    ",ans);
        fclose(stdin);
        fclose(stdout);
        return 0;
    }
    View Code

      

     

       琪露诺:https://www.luogu.org/problemnew/show/P1725

       解题思路:用dp[i]表示到达点i时获得的最大冰冻指数,dp[i]=max{dp[i-j]}+a[i],l<=j<=r<=i.

            然后对于求max{dp[i-j]}用单调队列来优化。

     

    #include<bits/stdc++.h>
    using namespace std;
    #define re register int
    #define ll long long
    #define maxn 200009
    #define maxm
    inline ll read()
    {
        ll x=0,f=1;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ll)(ch-'0');ch=getchar();}
        return x*f;
    }
    int q[maxn],a[maxn],dp[maxn];
    int n,m,k,ans,tot,head,tail,l,r; 
    int main()
    {
    //    freopen(".in","r",stdin);
    //    freopen(".out","w",stdout);
        n=read(),l=read(),r=read();
        for(int i=0;i<=n;i++)
            a[i]=read();
        head=1,tail=0;
        for(int i=l;i<=n;i++)
        {
            while(head<=tail&&i-q[head]>r)
                head++;
            while(head<=tail&&dp[q[tail]]<=dp[i-l])
                tail--;
            q[++tail]=i-l;
            dp[i]=dp[q[head]]+a[i];
        }
        ans=0;
        for(int i=n-r+1;i<=n;i++)
            ans=max(ans,dp[i]);
        printf("%d
    ",ans);
        fclose(stdin);
        fclose(stdout);
        return 0;
    }
    View Code

     

       最后推荐一个博客:https://blog.csdn.net/hjf1201/article/details/78729320

      里面单调队列优化DP的基本题型都有,比较齐全。

     

      

  • 相关阅读:
    Error:Execution failed for task ':app:transformClassesWithDexForDebug'.
    Appcan——Box
    基于SSH框架的网上商城的质量属性
    框架的选择
    软件体系结构的认识
    Cocos2d-x项目创建方式
    一、设计模式简介
    数组与字符串四(例子、使用(2))
    数组与字符串三(Cocos2d-x 3.x _Array容器)
    数组与字符串二(例子、使用)
  • 原文地址:https://www.cnblogs.com/Dxy0310/p/9742045.html
Copyright © 2011-2022 走看看