单调队列定义:

  其实单调队列就是一种队列内的元素有单调性的队列,因为其单调性所以经常会被用来维护区间最值或者降低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]所以要初始化为0for(int i=1;i<=n;i++)//每次队首的元素就是当前的答案
    {printf("%d\n",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\n",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\n",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\n",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\n",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\n",ans);fclose(stdin);fclose(stdout);return 0;
}

View Code

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

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

  

转载于:https://www.cnblogs.com/Dxy0310/p/9742045.html

单调队列以及单调队列优化DP相关推荐

  1. 算法笔记--单调队列优化dp

    单调队列:队列中元素单调递增或递减,可以用双端队列实现(deque),队列的前面和后面都可以入队出队. 单调队列优化dp: 问题引入: dp[i] = min( a[j] ) ,i-m < j ...

  2. poj 2373(单调队列优化dp)

    在长为L(<=1000000)的草地(可看成线段)上装喷水头,喷射是以这个喷水头为中心,喷水头的喷洒半径是可调节的调节范围为[a,b].要求草地的每个点被且只被一个喷水头覆盖,并且有些连续区间必 ...

  3. poj 1821(单调队列优化dp)

    题意:有一道线性篱笆由N个连续的木板组成.有K个工人,你要叫他们给木板涂色.每个工人有3个参数:L 表示 这个工人可以涂的最大木板数目,S表示这个工人站在哪一块木板,P表示这个工人每涂一个木板可以得到 ...

  4. 洛谷P3195 [HNOI2008]玩具装箱TOY(单调队列优化DP)

    题目描述 P教授要去看奥运,但是他舍不下他的玩具,于是他决定把所有的玩具运到北京.他使用自己的压缩器进行压缩,其可以将任意物品变成一堆,再放到一种特殊的一维容器中.P教授有编号为1...N的N件玩具, ...

  5. 【计蒜客 - 蓝桥训练】蒜厂年会(单调队列优化dp,循环数列的最大子段和)

    题干: 在蒜厂年会上有一个抽奖,在一个环形的桌子上,有 nn 个纸团,每个纸团上写一个数字,表示你可以获得多少蒜币.但是这个游戏比较坑,里面竟然有负数,表示你要支付多少蒜币.因为这些数字都是可见的,所 ...

  6. bzoj1233 单调队列优化dp

    https://www.lydsy.com/JudgeOnline/problem.php?id=1233 数据结构优化dp的代码总是那么抽象 题意:奶牛们讨厌黑暗. 为了调整牛棚顶的电灯的亮度,Be ...

  7. POJ1821 单调队列//ST表 优化dp

    http://poj.org/problem?id=1821 当我们在考虑内层循环j以及决策k的时候,我们可以把外层变量i看作定值,以此来优化dp状态转移方程. 题意 有n个工人准备铺m个连续的墙,每 ...

  8. 【codevs4654】【BZOJ2442】修剪草坪,第一次的单调队列,优化DP

    传送门1 传送门2(仅权限号) 写在前面:终于回到老校了 思路:想学斜率优化DP-->学了学单调队列,发现很厉害的样子. 网上主要有两种DP思路,我这里写的是记录所能取得的最优值,即f[i]指第 ...

  9. POJ 1821 Fence(单调队列优化DP)

    题解 以前做过很多单调队列优化DP的题. 这个题有一点不同是对于有的状态可以转移,有的状态不能转移. 然后一堆边界和注意点.导致写起来就很难受. 然后状态也比较难定义. dp[i][j]代表前i个人涂 ...

最新文章

  1. 将文件上传至ftp服务器,FTP文件上传工具类,将文件上传至服务器指定目录
  2. 不愿意和别人打交道_不想麻烦别人,也不希望别人麻烦我,是什么心理?看完就明白...
  3. Jmeter连接到Mysql
  4. 如何在CSDN博客中显示图片而不是链接
  5. leetcode237 删除链表中的节点(你意想不到的做法,注意细节)
  6. 用python画多来a梦-使用Python的Turtle绘制哆啦A梦实例
  7. hdu2846(字典树)
  8. MAC读写NTFS移动硬盘的解决办法
  9. HTML5 语义元素
  10. chrome等浏览器滚动全屏截图方法
  11. 詹姆斯高斯林_詹姆斯·高斯林(James Gosling)关于Java的立场:我几乎不在乎
  12. 写给学生看的系统分析与验证笔记(一)——形式化基础
  13. ubuntu18.04+cuda9.0+lenovo y430p(GTX850M)亲测可用
  14. python易盾滑动验证码
  15. 虚拟化KVM技术详解
  16. TCP/IP前端面试
  17. 如何自己写一门简单的编程语言(解释型语言)
  18. (转)我所经历的大数据平台发展史(四):互联网时代 • 下篇
  19. 【业界首创】攻防演练“杀手锏”!金睛云华HVV超融合一体机震撼登场!
  20. C# 使用SMS接口实现手机短信发送功能

热门文章

  1. Service实现文件下载
  2. Android(java)学习笔记69:短信发送器
  3. 【转】博客园中应用LaTex进行公式编写
  4. ora-24247:网络访问被访问控制列表(ACL)拒绝
  5. 技术管理中的“沟通”
  6. CSS单位--px,em,rem,rpx区别
  7. kingbase7获取唯一索引和子分区键的view
  8. BIO ,NIO,AIO的区别
  9. POJ:3461-Oulipo(KMP模板题)
  10. 一张图轻松搞懂javascript event对象的clientX,offsetX,screenX,pageX区别