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

    最近几天接触了单调队列,还接触了单调栈,就总结一下。
    其实单调队列,和单调栈都是差不多的数据类型,顾名思义就是在栈和队列上加上单调,单调递增或者单调递减。当要入栈或者入队的时候,要和栈头或者队尾进行比较,满足单调的性质则入队入栈,否则将当前元素删去,直到满足单调性质。
    那么问题来了,单调队列,和单调栈有什么用了。最普遍的最重要的作用就是起到优化的作用。当然我目前也只知道这个所用。
    先看一道例题:
    求一个n序列中,所有长度不大于lmaxin,的连续子序列中,序列和最大的是多少。最容易想到的方法,效率是O(n*lmaxin);

              for(int i=0;i<n;i++)
              {
                int s=0;
                for(int j=i;j>=max(0,i-lmaxin+1);j--)
                {
                    s+=a[i];
                    ans=max(ans,s);
                }
    
             }
    

    上面的代码中其实有很多都是重复比较了,所以效率低
    单调队列优化的代码

            rear=head=0;
            for(int i=1;i<=n;i++)
            {
               while(rear>=front&&s[i]<q[rear-1])
               {rear--;}
               q[rear++]=i;
               while(q[front]<i-m)
                   front++;
               ans=max(ans,s[i]-s[q[front]]);
             }
    

    s数组是前缀和,给定一个端点,求端点左边长度为m的区间的最小值就可以了。其实就相当于把原来的长度为m的for循环变成单调队列。区间长度是固定的m,单调队列求解区间的最小值,比暴力的for循环要高效率的多,因为元素只是一进一出,效率是O(n)。那么联系到单调栈,那么单调栈可以优化区间长度不断增加且左端点固定的最大值(个人联想)。

    那么经常听到的单调队列优化背包的也很好理解了。给一道题目多重背包单调队列优化,hdu上面不用优化,poj需要优化
    http://poj.org/problem?id=1742
    hdu AC的代码

    #include <iostream>
    #include <string.h>
    #include <math.h>
    #include <algorithm>
    #include <stdlib.h>
    
    using namespace std;
    #define MIN -9999
    int dp[100005];
    int n;
    int m;
    int a[100005];
    int b[1005];
    void ZeroOnePack(int v,int w)
    {
        for(int i=m;i>=w;i--)
        {
            if(dp[i-w]!=MIN)
                dp[i]=max(dp[i],dp[i-w]+v);
        }
    }
    void CompletePack(int v,int w)
    {
        for(int i=w;i<=m;i++)
        {
            if(dp[i-w]!=MIN)
                dp[i]=max(dp[i],dp[i-w]+v);
        }
    }
    void MultiplyPack(int v,int w,int c)
    {
        if(c*w>m)
        {
            CompletePack(v,w);
            return ;
        }
        int k;
        k=1;
        while(k<c)
        {
            ZeroOnePack(k*v,k*w);
            c=c-k;
            k<<=1;
        }
        ZeroOnePack(c*v,c*w);
    }
    int main()
    {
        int result;
        while(scanf("%d%d",&n,&m)!=EOF)
        {
            result=0;
            if(n==0&&m==0)
                break;
            for(int i=1;i<=n;i++)
              scanf("%d",&a[i]);
            for(int i=1;i<=n;i++)
              scanf("%d",&b[i]);
            for(int i=0;i<=m;i++)
                dp[i]=MIN;
            dp[0]=0;
            for(int i=1;i<=n;i++)
                 MultiplyPack(a[i],a[i],b[i]);
             for(int i=1;i<=m;i++)
             {
                 if(dp[i]!=MIN)
                     result++;
             }
            printf("%d
    ",result);
        }
        return 0;
    
    }

    poj 单调队列优化的POJ的代码

    #include <iostream>
    #include <string.h>
    #include <stdlib.h>
    #include <algorithm>
    #include <math.h>
    
    using namespace std;
    #define MAX 100000
    int dp[MAX+5];
    int a[105];
    int c[105];
    int n,m;
    int rear;
    int front;
    int queue[MAX+5];
    int ans;
    int main()
    {
        while(scanf("%d%d",&n,&m)!=EOF)
        {
            if(n==0&&m==0)
                break;
            ans=0;
            for(int i=0;i<n;i++)
                scanf("%d",&a[i]);
            for(int j=0;j<n;j++)
                scanf("%d",&c[j]);
            memset(dp,0,sizeof(dp));
            dp[0]=1;
            for(int i=0;i<n;i++)
            {
                if(c[i]==1)
                {
                    for(int j=m;j>=a[i];j--)
                        if(!dp[j]&&dp[j-a[i]])
                            dp[j]=1;
                }
                else if(c[i]*a[i]>=m)
                {
                    for(int j=a[i];j<=m;j++)
                        if(!dp[j]&&dp[j-a[i]])
                            dp[j]=1;
                }
                else
                {
                    for(int k=0;k<a[i];k++)
                    {
                        rear=0;front=0;int sum=0;
                        for(int j=k;j<=m;j+=a[i])
                        {
                            if((rear-front)==c[i]+1)
                                sum-=queue[front++];
                            queue[rear++]=dp[j];
                            sum+=dp[j];
                            if(!dp[j]&&sum)
                                dp[j]=1;
                        }
                    }
                }
            }
            for(int i=1;i<=m;i++)
                if(dp[i]) ans++;
            printf("%d
    ",ans);
        }
    }

    第一个是倍增方法,第二个是单调队列优化的方法,其实不用分成0 1背包和完全背包,直接用单调队列就可以。多重背包优化,要把根据余数把体积分成几类
    参考这个资料的

    之间用倍增法写的多重背包都可以用单调队列优化
    http://acm.hdu.edu.cn/showproblem.php?pid=2191
    可以试试用单调队列进行优化

    //
    //  main.cpp
    //  单调队列优化1
    //
    //  Created by 陈永康 on 16/1/16.
    //  Copyright &#169; 2016年 陈永康. All rights reserved.
    //
    
    #include <iostream>
    #include <string.h>
    #include <stdlib.h>
    #include <math.h>
    #include <algorithm>
    
    using namespace std;
    int c[105];
    int w[105];
    int v[105];
    int dp[105];
    int n,m;
    int b[105];
    int a[105];
    int rear,front;
    int main()
    {
        int t;
        scanf("%d",&t);
        while(t--)
        {
            memset(dp,0,sizeof(dp));
            scanf("%d%d",&m,&n);
            for(int i=0;i<n;i++)
                scanf("%d%d%d",&w[i],&v[i],&c[i]);
            for(int i=0;i<n;i++)
            {
                for(int j=0;j<w[i];j++)
                {
                        rear=front=0;
                        for(int k=0;k<=(m-j)/w[i];k++)
                        {
                            int x=k;
                            int y=dp[k*w[i]+j]-k*v[i];
                            while(front<rear&&y>=b[rear-1])
                                rear--;
                            a[rear]=x;
                            b[rear++]=y;
                            while(a[front]<k-c[i])
                                front++;
                            dp[k*w[i]+j]=b[front]+k*v[i];
                  }
    
                }
            }
            printf("%d
    ",dp[m]);
        }
        return 0;
    }

    单调队列不仅可以优化多重背包,可以优化别的DP问题
    一道好题目
    这是时间超限的代码

    //
    //  main.cpp
    //  单调队列优化3
    //
    //  Created by 陈永康 on 16/1/19.
    //  Copyright &#169; 2016年 陈永康. All rights reserved.
    //
    
    #include <iostream>
    #include <string.h>
    #include <math.h>
    #include <stdlib.h>
    #include <algorithm>
    
    using namespace std;
    #define MAX 1<<30
    int dp[2005][2005];
    int w,t,MaxP;
    int buy[2005];
    int sell[2005];
    int MaxB[2005];
    int MaxS[2005];
    int main()
    {
        int cas;
        scanf("%d",&cas);
        while(cas--)
        {
            scanf("%d%d%d",&t,&MaxP,&w);
            for(int i=1;i<=t;i++)
                scanf("%d%d%d%d",&buy[i],&sell[i],&MaxB[i],&MaxS[i]);
            for(int i=1;i<=2000;i++)
                for(int j=1;j<=2000;j++)
                    dp[i][j]=-MAX;
            for(int i=0;i<=MaxB[1];i++)
                dp[1][i]=0-buy[1]*i;
            for(int i=2;i<=t;i++)
            {
                for(int j=0;j<=MaxP;j++)
                {
                    for(int k=1;k<=MaxP;k++)
                    {
                        if(i-w-1<=0||j-k>MaxB[i]||j<=k)
                            continue;
                        if(dp[i-w-1][k]==MAX)
                            continue;
                        dp[i][j]=max(dp[i][j],dp[i-w-1][k]-buy[i]*(j-k));
                    }
                    for(int k=1;k<=MaxP;k++)
                    {
                        if(i-w-1<=0||k<=j||k-j>MaxS[i])
                            continue;
                        if(dp[i-w-1][k]==MAX)
                            continue;
                        dp[i][j]=max(dp[i][j],dp[i-w-1][k]+sell[i]*(k-j));
                    }
                    dp[i][j]=max(dp[i][j],dp[i-1][j]);
                }
            }
            int ans=0;
            for(int j=0;j<=MaxP;j++)
                ans=max(ans,dp[t][j]);
            printf("%d
    ",ans);
    
    
        }
        return 0;
    }

    状态转移方程不难想到,但是写出来就是时间超限的,用单调队列优化的代码

    //
    //  main.cpp
    //  单调队列优化3
    //
    //  Created by 陈永康 on 16/1/19.
    //  Copyright &#169; 2016年 陈永康. All rights reserved.
    //
    
    #include <iostream>
    #include <string.h>
    #include <math.h>
    #include <stdlib.h>
    #include <algorithm>
    
    using namespace std;
    #define MAX 1<<30
    int dp[2005][2005];
    int w,t,MaxP;
    int buy[2005];
    int sell[2005];
    int MaxB[2005];
    int MaxS[2005];
    int a[2005];
    int b[2005];
    int rear;
    int front;
    int main()
    {
        int cas;
        scanf("%d",&cas);
        while(cas--)
        {
            scanf("%d%d%d",&t,&MaxP,&w);
            for(int i=1;i<=t;i++)
                scanf("%d%d%d%d",&buy[i],&sell[i],&MaxB[i],&MaxS[i]);
            for(int i=1;i<=2000;i++)
                for(int j=1;j<=2000;j++)
                    dp[i][j]=-MAX;
            for(int i=1;i<=t;i++)
               for(int j=0;j<=min(MaxP,MaxB[i]);j++)
                     dp[i][j]=0-buy[i]*j;
            for(int i=2;i<=t;i++)
            {
                for(int j=0;j<=MaxP;j++)
                    dp[i][j]=max(dp[i][j],dp[i-1][j]);
                if(i-w-1<=0)
                    continue;
                rear=front=0;
                b[rear]=dp[i-w-1][0];
                a[rear]=0;
                for(int j=1;j<=MaxP;j++)
                {
    
                    int x=j;
                    int y=dp[i-w-1][j];
                    while(front<=rear&&b[rear]-(j-a[rear])*buy[i]<y)
                        rear--;
                    b[++rear]=y;
                    a[rear]=x;
                    while(front<=rear&&a[front]+MaxB[i]<j)
                        front++;
                    dp[i][j]=max(dp[i][j],b[front]-(j-a[front])*buy[i]);
    
                }
                rear=front=0;
                b[rear]=dp[i-w-1][MaxP];
                a[rear]=MaxP;
                for(int j=MaxP-1;j>=0;j--)
                {
                    int x=j;
                    int y=dp[i-w-1][j];
                    while(front<=rear&&b[rear]+(a[rear]-j)*sell[i]<y)
                        rear--;
                    b[++rear]=y;
                    a[rear]=x;
                    while(front<=rear&&a[front]-MaxS[i]>j)
                        front++;
                    dp[i][j]=max(dp[i][j],b[front]+(a[front]-j)*sell[i]);
                }
    
            }
            int ans=0;
            for(int j=0;j<=MaxP;j++)
                ans=max(ans,dp[t][j]);
            printf("%d
    ",ans);
    
    
        }
        return 0;
    }

    说了这么多单调队列的,就说一下单调栈的应用
    http://poj.org/problem?id=2082
    题目的意思就是给你一系列矩形,求最大的矩形面积
    这道题目可以用暴力的方法O(n^2),用单调栈的话就是O(n);
    单调栈是递增的,这个单调栈在入栈的时候要合并矩形,合并之后再入栈。
    暴力ac的代码

    #include <iostream>
    #include <string.h>
    #include <math.h>
    #include <stdlib.h>
    #include <algorithm>
    
    using namespace std;
    #define MAX 50000
    struct Node
    {
        int w;
        int h;
    }a[MAX+5];
    int n;
    int ans;
    int sum;
    int main()
    {
        while(scanf("%d",&n)!=EOF)
        {
            if(n==-1)
                break;
            ans=0;
            sum=0;
            for(int i=0;i<n;i++)
                scanf("%d%d",&a[i].w,&a[i].h);
            for(int i=0;i<n;i++)
            {
                sum=0;
                for(int j=i+1;j<n;j++)
                {
                    if(a[j].h>=a[i].h)
                        sum+=a[i].h*a[j].w;
                    else
                        break;
                }
                for(int p=i-1;p>=0;p--)
                {
                    if(a[p].h>=a[i].h)
                        sum+=a[i].h*a[p].w;
                    else
                        break;
                }
                sum+=a[i].w*a[i].h;
                ans=max(ans,sum);
    
            }
            printf("%d
    ",ans);
        }
        return 0;
    
    
    }

    单调栈优化的代码

    #include <iostream>
    #include <string.h>
    #include <stdlib.h>
    #include <algorithm>
    #include <math.h>
    #include <stack>
    
    using namespace std;
    #define MAX 50000
    int n;
    struct Node
    {
        int x,y;
    }a[MAX+5];
    stack<Node> Stack;
    int ans;
    int main()
    {
        while(scanf("%d",&n)!=EOF)
        {
    
            if(n==-1)
                break;
            while(!Stack.empty())
                Stack.pop();
            ans=0;
            for(int i=0;i<n;i++)
                scanf("%d%d",&a[i].x,&a[i].y);
            Stack.push(a[0]);
            for(int i=1;i<n;i++)
            {
                int sum=0;
                Node term=Stack.top();
                while(term.y>a[i].y)
                {
                    sum+=term.x;
                    ans=max(ans,sum*term.y);
                    Stack.pop();
                    if(Stack.empty())
                        break;
                    term=Stack.top();
                }
                Node temp;
                temp.x=sum+a[i].x;
                temp.y=a[i].y;
                Stack.push(temp);
            }
            int sum=0;
            while(!Stack.empty())
            {
                Node term=Stack.top();
                sum+=term.x;
                ans=max(ans,sum*term.y);
                Stack.pop();
            }
            printf("%d
    ",ans);
        }
    }

    这里并不能看出单调栈的求区间最大值的功能,反而是运用了单调栈单调的特性,进行求解。这也是单调栈,单调队列这种数据结构有魅力的地方吧

  • 相关阅读:
    2020.11.17
    2020.11.26
    2020.11.18
    2020.12.01
    2020.11.23
    Java编程规范
    20201003 千锤百炼软工人
    2020081920200825 千锤百炼软工人
    20201004 千锤百炼软工人
    20200929 动手动脑
  • 原文地址:https://www.cnblogs.com/dacc123/p/8228806.html
Copyright © 2011-2022 走看看