Tip:还有很多更有深度的题目,这里不再给出,只给了几道基本的题目(本来想继续更的,但是现在做的题目不是这一块内容,以后有空可能会继续补上)

单调队列——看起来就是很高级的玩意儿,显然是个队列,而且其中的元素还具有单调性

当然,它不只只是一个简单的队列,还是一个双端队列,即队首队尾都可以弹出元素,当然可以用C++自带的STL<deque>实现,当然这篇博客里不建议使用这种写法,因为不开O2的话就会有一个大弊端——

单调队列裸题:滑动窗口

  线性的求一个区间内的最值,我们先来找个规律,比如这组数据:

  8 3
  1 3 -1 -3 5 3 6 7

(以求最大值为例子)

  我们发现前两个数中 1  3   ,3的优先级明显大于1,原因?3比1大,而且3还在1的右边(这样在后面的更新中3还能起到作用,而1显然已经没有作用了,那么我们就可以把1从队列里面弹出去了!)当然,这个操作在3丢到队列里面的时候就可以进行了,同时在这个操作之前,我们还要把前面的元素和现在位置差距大于k的元素弹掉

  这样我们便可以保证队列中的元素是单调递增的,那么我们每次在n中取出k个元素的时候,只要把当前队列中的第k个元素放到输出列表中就好了!

  当然最小值也是一样的,维护一个单调递减的序列,在这个过程中,每次输出其中最小数

代码如下:(先求最小值,后求最大值)

  1. #include<cstdio>
  2. #include<iostream>
  3. #include<cstring>
  4. #include<algorithm>
  5. using namespace std;
  6. inline int read(){
  7. int ans=,f=; char chr=getchar();
  8. while(!isdigit(chr)){if(chr=='-') f=-;chr=getchar();}
  9. while(isdigit(chr)) {ans=(ans<<)+chr-;chr=getchar();}
  10. return ans*f;
  11. }
  12. void write(int x){
  13. if(x<) x=-x,putchar('-');
  14. if(x>) write(x/);
  15. putchar(x%+);
  16. }
  17. int q[],h,t,n,a[],k;
  18. int main(){
  19. n=read();k=read();
  20. for(register int i=;i<=n;++i) a[i]=read();
  21. h=,t=;
  22. for(register int i=;i<=n;++i){//Min
  23. while(h<=t&&q[h]+k<=i) ++h;
  24. while(h<=t&&a[i]<=a[q[t]]) --t;
  25. q[++t]=i;
  26. if(i>=k) write(a[q[h]]),putchar(' ');
  27. }puts("");
  28. h=,t=;
  29. for(register int i=;i<=n;++i){//Max
  30. while(h<=t&&q[h]+k<=i) ++h;
  31. while(h<=t&&a[i]>=a[q[t]]) --t;
  32. q[++t]=i;
  33. if(i>=k) write(a[q[h]]),putchar(' ');
  34. }
  35. return ;
  36. }

【时间复杂度分析】

   显然外循环的复杂度为n,关键在于其中的while循环,分析一下可以知道,每一个元素在其中只会进入队列一次,出队列一次,所以总的时间复杂度为O(n),而且常数也是十分优秀的

     当然像这种求区间最值的问题也有一种简单粗暴的方法:线段树

代码如下:(这里代码就折叠掉了,有需求的读者可以自己阅读,就是求区间的最大值和最小值,连修改都不用,可以说是线段树的模板了)

  1. // luogu-judger-enable-o2
  2. #include<iostream>
  3. #include<cstdio>
  4. #include<cctype>
  5. #include<algorithm>
  6. #include<cstring>
  7. #define ll long long
  8. #define lson i << 1,l,m
  9. #define rson i << 1| 1,m + 1,r
  10. #define MAXN (int)1e6 + 5
  11. using namespace std;
  12.  
  13. inline ll read(){
  14. char chr=getchar();
  15. ll f=,ans=;
  16. while(!isdigit(chr)) {if(chr=='-') f=-;chr=getchar();}
  17. while(isdigit(chr)) {ans=ans*;ans+=chr-'';chr=getchar();}
  18. return ans*f;
  19.  
  20. }
  21.  
  22. void write(ll x){
  23. if(x<){
  24. putchar('-');
  25. x=-x;
  26. }
  27. if(x<)
  28. putchar(x+'');
  29. else
  30. write(x/),putchar(x%+);
  31. }
  32.  
  33. struct P{
  34. ll l,r;
  35. ll max,add,min;
  36. ll mid(){
  37. return l + r >> ;
  38. }
  39. }t[MAXN << ];
  40. ll a[MAXN << ];
  41.  
  42. void build(ll i,ll l,ll r){
  43. t[i].l = l;t[i].r = r;
  44. if(l == r){
  45. t[i].min = a[l];
  46. t[i].max = a[l];
  47. return;
  48. }
  49. ll m = t[i].mid();
  50. build(lson);
  51. build(rson);
  52. t[i].max =max( t[i << ].max , t[i << | ].max );
  53. t[i].min =min( t[i << ].min , t[i << | ].min );
  54. }
  55.  
  56. ll qmin(ll i,ll l,ll r){
  57. if(l <= t[i].l && t[i].r <= r) return t[i].min;
  58. ll pp = 0x3f3f3f3f,qq = 0x3f3f3f3f;
  59. ll m = t[i].mid();
  60. if(l <= m) pp = qmin(i << ,l,r);
  61. if(r > m) qq = qmin(i << | ,l,r);
  62. return min(qq , pp);
  63. }
  64.  
  65. ll qmax(ll i,ll l,ll r){
  66. if(l <= t[i].l && t[i].r <= r) return t[i].max;
  67. ll pp = -0x3f3f3f3f,qq = -0x3f3f3f3f;
  68. ll m = t[i].mid();
  69. if(l <= m) pp = qmax(i << ,l,r);
  70. if(r > m) qq = qmax(i << | ,l,r);
  71. return max(qq , pp);
  72. }
  73.  
  74. ll n,m;
  75. int main(){
  76. n = read() ;
  77. m = read() ;
  78. for(ll i = ;i <= n;i ++)
  79. a[i] = read();
  80. build(,,n);
  81. for(int i = ;i + m - <= n;++i){
  82. printf("%lld",qmin(,i,i+m-));
  83. putchar(' ');
  84. }
  85. puts("");
  86. for(int i = ;i + m - <= n;++i){
  87. printf("%lld",qmax(,i,i+m-));
  88. putchar(' ');
  89. }
  90. return ;
  91. }

      

【NO.1】

  【NOIP提高组初赛程序填空】烽火传递

题目描述

烽火台是重要的军事防御设施,一般建在交通要道或险要处。一旦有军情发生,则白天用浓烟,晚上有火光传递军情。

在某两个城市之间有 nn 座烽火台,每个烽火台发出信号都有一定的代价。为了使情报准确传递,在连续 mm 个烽火台中至少要有一个发出信号。现在输入 n,mn,m 和每个烽火台的代价,请计算总共最少的代价在两城市之间来准确传递情报。

输入格式

第一行是 n,mn,m,表示 nn 个烽火台和连续烽火台数 mm;

第二行 nn 个整数表示每个烽火台的代价 a_iai​。

输出格式

输出仅一个整数,表示最小代价。

样例

样例输入

  1. 5 3
  2. 1 2 5 6 2

样例输出

  1. 4

样例说明

在第 2,52,5 号烽火台上发信号。

数据范围与提示

n,m正整数且小于等于2×10^5

【分析】

  显然是一道DP题

  不妨令f[i]表示取第i个点时的最小值

  那么有方程:

f[i]=min{f[k]}+a[i]

其中k∈[i-m,i-1]

  但显然这样是O(n^2)的复杂度,对于十万级的n,m显然不够优,如果我们可以在很短的时间内求出f[k](k∈[i-m,i-1])的最小值就好了,并且随着i的增大,它每次还能更新入新的数据!

  区间最小?单点更新?不就是线段树吗!O(nlogn)的算法出炉(还是一样,先把代码折叠起来,有需求的读者可以自己点开看):(当然,如果读者不会线段树,可以跳过这一部分的代码直接阅读后面)

  1. #include<iostream>
  2. #include<cstdio>
  3. #include<cstring>
  4. #include<algorithm>
  5. #define int long long
  6. using namespace std;
  7. inline int read(){
  8. char chr=getchar(); int f=,ans=;
  9. while(!isdigit(chr)) {if(chr=='-') f=-;chr=getchar();}
  10. while(isdigit(chr)) {ans=(ans<<)+(ans<<);ans+=chr-'';chr=getchar();}
  11. return ans*f;
  12. }
  13. void write(int x){
  14. if(x<) putchar('-'),x=-x;
  15. if(x>) write(x/);
  16. putchar(x%+'');
  17. }
  18. int n,m,a[<<];
  19. int minn[<<];
  20. void updata(int i,int l,int r,int pos,int x){
  21. if(l==r){minn[i]=x;return;}
  22. int mid=l+r>>;
  23. if(pos<=mid) updata(i<<,l,mid,pos,x);
  24. else updata(i<<|,mid+,r,pos,x);
  25. minn[i]=min(minn[i<<],minn[i<<|]);
  26. }
  27. int query(int i,int l,int r,int ql,int qr){
  28. if(ql<=l&&r<=qr){return minn[i];}
  29. int mid=l+r>>,x=0x3f3f3f3f,y=0x3f3f3f3f;
  30. if(ql<=mid) x=query(i<<,l,mid,ql,qr) ;
  31. if(qr>mid) y=query(i<<|,mid+,r,ql,qr);
  32. return min(x,y);
  33. }
  34. int f[<<];
  35. signed main(){
  36. freopen("ttt.in","r",stdin);
  37. n=read();m=read();
  38. for(int i=;i<=n;i++)a[i]=read();
  39. for(int i=;i<m;i++) updata(,,n,i,a[i]);
  40. for(int i=m;i<=n;i++){
  41. f[i]=query(,,n,i-m,i-)+a[i];
  42. updata(,,n,i,f[i]);
  43. }int ans=0x7fffffff;
  44. for(int i=n-m+;i<=n;i++) ans=min(f[i],ans);
  45. write(ans);
  46. return ;
  47. }

  对于本题,已经可以在要求的时间内求出答案了,但是显然这样的办法有点异常暴力,而且还要照顾一下不会线段树的童鞋是吧,于是切入正题,如何用单调队列做这题!

  对于每一次状态的转移,我们只需要维护f[]数组的最值即可,那么显然我们可以以f[]创建一个单调队列,维护f[]的最小值,每次更新它的最新元素,且每次更新即取队首元素即可

  当然,上面的while循环可以换成在C++更加里面更加灵活的for循环,本质上还是一个求最值的问题,不过在这之前我们要能从中推出转移方程,关键是要从递推式中看出单调性,这也是我们用单调队列解题的前提

  1. #include<iostream>
  2. #include<cstdio>
  3. #include<cstring>
  4. #include<algorithm>
  5. #define int long long
  6. using namespace std;
  7. inline int read(){
  8. char chr=getchar(); int f=,ans=;
  9. while(!isdigit(chr)) {if(chr=='-') f=-;chr=getchar();}
  10. while(isdigit(chr)) {ans=(ans<<)+(ans<<);ans+=chr-'';chr=getchar();}
  11. return ans*f;
  12. }
  13. void write(int x){
  14. if(x<) putchar('-'),x=-x;
  15. if(x>) write(x/);
  16. putchar(x%+'');
  17. }
  18. const int N=1e6+;
  19. int n,m,l,r,a[N],f[N],q[N<<];
  20. signed main(){
  21. n=read();m=read();
  22. for(int i=;i<=n;i++) a[i]=read();
  23. l=r=;
  24. for(int i=;i<=n;i++){
  25. for(;l<r&&i-q[l]>m;l++);
  26. f[i]=f[q[l]]+a[i];
  27. for(;l<r&&f[q[r]]>f[i];r--);
  28. q[++r]=i;
  29. }int ans=0x7fffffff;
  30. for(int i=n-m+;i<=n;i++) ans=min(ans,f[i]);//这里有一个小细节,最后一次选择可以从最后m个元素中选择,原因很简单,只要保证后面m个元素中有一个取就够了
  31. cout<<ans;
  32. return ;
  33. }

【NO.2】

  【Tyvj1305】最大子序和

【问题描述】

  1. 输入一个长度为n的整数序列,从中找出一段不超过M的连续子序列,使得整个序列的和最大。
  2. 例如 1,-3,5,1,-2,3
  3. m=4时,S=5+1-2+3=7
  4. m=2m=3时,S=5+1=6

【输入格式】

  1. 第一行两个数n,m
  2. 第二行有n个数,要求在n个数找到最大子序和。

【输出格式】

  1. 一个数,数出他们的最大子序和。

【输入样例】

  1. 6 4
  2. 1 -3 5 1 -2 3

【输出样例】

  1. 7

【数据范围】

  1. n,m300000;数列元素的绝对值≤1000

【题目来源】

  1. Tyvj1305

【问题分析】  

  首先,要求连续我们可以把原序列转化成前缀和进行求解

  因为前缀和的性质有sum[l~r]=sum[r]-sum[l-1],对于每一个r,我们只要求出前面m个数中最小的sum[l]即可保证sum[l~r]最大

  以sum[]建立单调队列即可

  

  1. #include<iostream>
  2. #include<cstdio>
  3. #include<cstring>
  4. #include<algorithm>
  5. using namespace std;
  6. inline int read(){
  7. char chr=getchar(); int f=,ans=;
  8. while(!isdigit(chr)) {if(chr=='-') f=-;chr=getchar();}
  9. while(isdigit(chr)) {ans=(ans<<)+(ans<<);ans+=chr-'';chr=getchar();}
  10. return ans*f;
  11. }
  12. void write(int x){
  13. if(x<) putchar('-'),x=-x;
  14. if(x>) write(x/);
  15. putchar(x%+'');
  16. }
  17. const int M=;
  18. int n,m;
  19. int q[M],a[M],h,t,f[M];
  20. int sum[M];
  21. int main(){
  22. n=read(),m=read();
  23. for(int i=;i<=n;i++) a[i]=read(),sum[i]=sum[i-]+a[i];
  24. int l=,r=;int ans=-0x7fffffff;
  25. for(int i=;i<=n;i++){
  26. for(;l<r&&i-q[l]>m;l++);
  27. ans=max(ans,sum[i]-sum[q[l]]);
  28. for(;l<r&&sum[q[r]]>=sum[i];r--);
  29. q[++r]=i;
  30. }
  31. cout<<ans;
  32. return ;
  33. }

【NO.3】

  【Hdu3530Subsequence

【问题描述】

  1. 给定一个包含n个整数序列,求满足条件的最长区间的长度:该区间内的最大数和最小数的差不小于m,且不大于k

【输入格式】

  1. 输入包含多组测试数据:对于每组测试数据:
  2. 第一行,包含三个整数nmk
  3. 第二行,包含n个整数的序列。

【输出格式】

  1. 对于每组测试数据,输出满足条件的最长区间的长度。

【输入样例】

  1. 5 0 0
  2. 1 1 1 1 1
  3. 5 0 3
  4. 1 2 3 4 5

【输出样例】

  1. 5
  2. 4

【数据范围】

  1. 1n100000;
  2. 0m,k100000;
  3. 0ai100000

【题目来源】

  1. Hdu3530

【题目分析】

  其实也是模板题了,只不过要求同时维护最大值最小值而已

  这里不再提最大值最小值的更新了,而是给出这道题里新的东西:要使最大值减最小值在区间[l,r]中的话,一旦当前的序列最大值减最小值不再该区间内了,便要继续弹出元素

  1. #include<iostream>
  2. #include<cstdio>
  3. #include<cstring>
  4. #include<algorithm>
  5. using namespace std;
  6. inline int read(){
  7. char chr=getchar(); int f=,ans=;
  8. while(!isdigit(chr)) {if(chr=='-') f=-;chr=getchar();}
  9. while(isdigit(chr)) {ans=(ans<<)+(ans<<);ans+=chr-'';chr=getchar();}
  10. return ans*f;
  11. }
  12. void write(int x){
  13. if(x<) putchar('-'),x=-x;
  14. if(x>) write(x/);
  15. putchar(x%+'');
  16. }
  17. const int M=;
  18. int q1[M],q2[M],a[M],n,m,k,t1,t2,tt1,tt2,ttt1,ttt2,ans;
  19. int main(){
  20. while(~scanf("%d%d%d",&n,&m,&k))
  21. {
  22. for(int i=;i<=n;i++) a[i]=read();
  23. memset(q1,,sizeof(q1));
  24. memset(q2,,sizeof(q2));
  25. t1=;t2=;ttt1=;ttt2=;ans=;tt1=;tt2=;
  26. for(int i=;i<=n;i++){
  27. while(t1<ttt1&&a[q1[ttt1-]]<=a[i])ttt1--; //maxn
  28. q1[ttt1++]=i;
  29. while(t2<ttt2&&a[q2[ttt2-]]>=a[i])ttt2--; //minn
  30. q2[ttt2++]=i;
  31. while(a[q1[t1]]-a[q2[t2]]>k)
  32. if(q1[t1]<q2[t2]) tt1=q1[t1++];
  33. else tt2=q2[t2++];
  34. if(a[q1[t1]]-a[q2[t2]]>=m)
  35. ans=max(ans,i-max(tt1,tt2));
  36. }
  37. write(ans),puts("");
  38. }
  39. return ;
  40. }

【专题系列】单调队列优化DP的更多相关文章

  1. 动态规划专题(四)——单调队列优化DP

    前言 单调队列优化\(DP\)应该还算是比较简单容易理解的吧,像它的升级版斜率优化\(DP\)就显得复杂了许多. 基本式子 单调队列优化\(DP\)的一般式子其实也非常简单: \[f_i=max_{j ...

  2. 单调队列优化DP,多重背包

    单调队列优化DP:http://www.cnblogs.com/ka200812/archive/2012/07/11/2585950.html 单调队列优化多重背包:http://blog.csdn ...

  3. bzoj1855: [Scoi2010]股票交易--单调队列优化DP

    单调队列优化DP的模板题 不难列出DP方程: 对于买入的情况 由于dp[i][j]=max{dp[i-w-1][k]+k*Ap[i]-j*Ap[i]} AP[i]*j是固定的,在队列中维护dp[i-w ...

  4. hdu3401:单调队列优化dp

    第一个单调队列优化dp 写了半天,最后初始化搞错了还一直wa.. 题目大意: 炒股,总共 t 天,每天可以买入na[i]股,卖出nb[i]股,价钱分别为pa[i]和pb[i],最大同时拥有p股 且一次 ...

  5. Parade(单调队列优化dp)

    题目连接:http://acm.hdu.edu.cn/showproblem.php?pid=2490 Parade Time Limit: 4000/2000 MS (Java/Others)    ...

  6. BZOJ_3831_[Poi2014]Little Bird_单调队列优化DP

    BZOJ_3831_[Poi2014]Little Bird_单调队列优化DP Description 有一排n棵树,第i棵树的高度是Di. MHY要从第一棵树到第n棵树去找他的妹子玩. 如果MHY在 ...

  7. 【单调队列优化dp】 分组

    [单调队列优化dp] 分组 >>>>题目 [题目] 给定一行n个非负整数,现在你可以选择其中若干个数,但不能有连续k个数被选择.你的任务是使得选出的数字的和最大 [输入格式] ...

  8. [小明打联盟][斜率/单调队列 优化dp][背包]

    链接:https://ac.nowcoder.com/acm/problem/14553来源:牛客网 题目描述 小明很喜欢打游戏,现在已知一个新英雄即将推出,他同样拥有四个技能,其中三个小技能的释放时 ...

  9. 单调队列以及单调队列优化DP

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

  10. BZOJ1791[Ioi2008]Island 岛屿 ——基环森林直径和+单调队列优化DP+树形DP

    题目描述 你将要游览一个有N个岛屿的公园.从每一个岛i出发,只建造一座桥.桥的长度以Li表示.公园内总共有N座桥.尽管每座桥由一个岛连到另一个岛,但每座桥均可以双向行走.同时,每一对这样的岛屿,都有一 ...

随机推荐

  1. Java中的接口详解

    接口 是Java语言中一种引用类型,是方法的集合,如果说类的内部封装了成员变量.构造方法和成员方法,那么接口的内部主要就是封装了方法,包含抽象方法(JDK 7及以前),默认方法和静态方法(JDK 8) ...

  2. Altium Designer 2017 ActiveRoute使用以及其他技巧

    ActiveRoute 点击右下角PCB->PCB ActiveRoute调出ActiveRoute面板 在设计电路时,有一堆细小的白色线,表示几个脚之间需要连接,按住键盘Alt + 鼠标左键, ...

  3. Linux内核系统调用处理过程

    原创作品转载请注明出处 + https://github.com/mengning/linuxkernel/ 学号末三位:168 下载并编译Linux5.0 xz -d linux-.tar.xz . ...

  4. tomcat 编码设置

    在Tomcat8.0之前的版本,如果你要向服务器提交中文是需要转码的(如果你没有修改server.xml中的默认编码),因为8.0之前Tomcat的默认编码为ISO8859-1. POST方式提交 r ...

  5. kesci---2019大数据挑战赛预选赛---情感分析

    一.预选赛题------文本情感分类模型 本预选赛要求选手建立文本情感分类模型,选手用训练好的模型对测试集中的文本情感进行预测,判断其情感为「Negative」或者「Positive」.所提交的结果按 ...

  6. 68.document增删改原理

    主要知识点 document增的原理 document删的原理 document改的原理 一.document增的原理 一个document存入es大致要分以下几个步骤 (1)数据写入buffer, ...

  7. Problem 56

    Problem 56 https://projecteuler.net/problem=56 Powerful digit sum A googol (10100) is a massive numb ...

  8. ansible - 基本用法

    目录 ansible - 01 一. 安装与使用 ansible命令格式 查看ansible生成的配置文件 ssh认证方式 ansible的第一个命令 弱口令校验 host-pattern的格式 模块 ...

  9. springcloud(五):Eureka提供数据的客户端连接Docker的mysql

    一.提供数据的客户端需要连接数据了,因此需要我们使用mybatis了,等下使用idea生成mybaits和web的依赖 二.提供数据的客户端项目 1.创建项目 2.选择idea自动给我们生成的依赖 3 ...

  10. JSTL 实现 为Select赋多个值

    需要注意需要在.jsp文件中引入相应的类库 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core ...