单调队列,单调栈总结

最近几天接触了单调队列,还接触了单调栈,就总结一下。
其实单调队列,和单调栈都是差不多的数据类型,顾名思义就是在栈和队列上加上单调,单调递增或者单调递减。当要入栈或者入队的时候,要和栈头或者队尾进行比较,满足单调的性质则入队入栈,否则将当前元素删去,直到满足单调性质。
那么问题来了,单调队列,和单调栈有什么用了。最普遍的最重要的作用就是起到优化的作用。当然我目前也只知道这个所用。
先看一道例题:
求一个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);
    }
}

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

原文地址:https://www.cnblogs.com/dacc123/p/8228806.html