zoukankan      html  css  js  c++  java
  • 单调队列入门

    本篇博客转自我很久以前在洛谷上写的一篇博客,原地址:https://www.luogu.org/blog/ybwowen/dan-diao-dui-lie

    单调队列是一种队列(废话

    其中队列的元素保证是单调递增或者是单调递减

    那么队首的元素不就是最小(或最大)的吗?

    我们结合具体的题目来看看吧:

    传送门:P1886 滑动窗口

    题目描述

    现在有一堆数字共N个数字(N<=10^6),以及一个大小为k

    的窗口。现在这个从左边开始向右滑动,每次滑动一个单

    位,求出每次滑动后窗口中的最大值和最小值。

    例如:

    The array is [1 3 -1 -3 5 3 6 7], and k = 3.

    输入格式:

    输入一共有两行,第一行为n,k。

    第二行为n个数(<INT_MAX).

    输出格式:

    输出共两行,第一行为每次窗口滑动的最小值

    第二行为每次窗口滑动的最大值

    输入输出样例

    输入样例#1:

    8 3

    1 3 -1 -3 5 3 6 7

    输出样例#1:

    -1 -3 -3 -3 3 3

    3 3 5 5 6 7

    这里我们只讨论最大值,最小值原理一样的

    解法1:

    如果按照常规方法,我们在求a[i] 即i~i+k-1区间内的最值

    时,要把区间内的所有数都访问一遍,时间复杂度约为

    (O(nk))。有没有一个快一点的算法呢?

    解法2:

    很明显,当我们在计算区间([i-k+1,i])的最大值时,是不

    是区间([i-k+1,i-1])我们之前已经计算过了?那么我们

    是不是要保存上一次的结果呢(主要是最大值)?

    这时,单调队列登场——

    单调队列主要有两个操作:删头去尾

    **1.删头 **

    如果队列头的元素离开了我们当前操作的区间,那么这

    个元素就没有任何用了,我们就要把它删掉

    2.去尾

    假设即将进入队列的元素为(X),队列尾指针为(tail)

    这时我们要比较二者的大小:

    1* (X<q[tail])

    此时q仍然保证着递减性,故直接将(X)插入队列尾

    2*(X>=q[tail])

    此时,队列递减性被打破,此时我们做一下操作:

    ① 弹出队尾元素

    因为当前的(q[tail])不但不是最大值,对于以后的情况

    也不如(X)更优,所以要弹出

    这好比当前队列尾部的元素是个能力不足的老兵,而新加入

    的新兵更年轻,更能打,当然不要老兵了

    ②重复执行①,直到满足(X<q[tail])或者队列为空为止

    ③将(X)插入队列

    对于样例而言:

    [1 3 -1] -3 5 3 6 7 
    q={1},{3},{3,-1} output:3//分别为每次操作的结果
    1 [3 -1 -3] 5 3 6 7 
    q={3,-1,-3} output:3
    1 3 [-1 -3 5] 3 6 7
    q={-1,-3},{-1,5},{5} output:5
    1 3 -1 [-3 5 3] 6 7
    q={5,3} output:5
    1 3 -1 -3 [5 3 6] 7	
    q={5,6},{6} output:6
    1 3 -1 -3 5 [3 6 7]
    q={6} output:7
    

    由于每个元素最多入队一次,出队一次(为什么?),所以

    时间复杂度为(O(n))

    当然,这题还可以用ST表和线段树来做,但都没有单调队列

    方便

    实现:

    由于要对队首和队尾进行维护,所以我们需要使用

    双端队列

    可以用STL中的deque,也可以手写

    代码://使用deque

    #include<bits/stdc++.h>
    using namespace std;
    int n,m;
    int read(){//快读 
    	char ch=getchar();
    	int f=1; int sum=0;
    	while(ch<'0'||ch>'9'){
    		if(ch=='-') f=-1;
    		ch=getchar();
    	}
    	while(ch>='0'&&ch<='9') sum=sum*10+ch-'0',ch=getchar();
    	return f*sum;
    }
    deque<int>q;
    int a[1000005];
    int main(){
    	n=read(); m=read();
    	for(int i=1;i<=n;i++) a[i]=read();
    	for(int i=1;i<=n;i++){//最小值 
    		while(!q.empty()&&a[q.back()]>a[i]) q.pop_back();//去尾 
    		q.push_back(i);
    		if(i>=m){
    			while(!q.empty()&&q.front()<=i-m) q.pop_front();//删头 
    			printf("%d ",a[q.front()]);
    		}
    	}
    	printf("
    ");
    	while(!q.empty()) q.pop_front();
    	for(int i=1;i<=n;i++){//最大值 
    		while(!q.empty()&&a[q.back()]<a[i]) q.pop_back();//去尾 
    		q.push_back(i);
    		if(i>=m){
    			while(!q.empty()&&q.front()<=i-m) q.pop_front();//删头 
    			printf("%d ",a[q.front()]);
    		}
    	}
    	printf("
    ");
    	return 0;
    }
    

    应用

    单调队列是一种小的数据结构,一半不单独出现,

    但是经常我们会遇到单调队列优化的DP

    举例:NOIP 2017 PJ 第四题 P3957跳房子

    二分+DP+单调队列优化,这里请大家仔细查阅题解

    某年NOIP TG 的初赛完善程序 烽火传递

    虽然这题你可以用二叉堆,但明显单调队列更好

    大家可以看看,这里我就不讲这么多了

    感谢阅读!

  • 相关阅读:
    孙鑫vc++学习(vs2008)笔记之第五课文字处理程序
    lesson2 流水灯
    lesson1 预备知识
    第二章 寄存器(CPU工作原理)
    孙鑫vc++学习(vs2008)笔记之第一课Windows程序运行原理
    孙鑫vc++学习(vs2008)笔记之第二课掌握C++
    孙鑫vc++学习(vs2008)笔记之第三课MFC内部运行原理
    第一章 基础知识
    小小说(文摘)
    孙鑫vc++学习(vs2008)笔记之第四课MFC消息映射、画图
  • 原文地址:https://www.cnblogs.com/ybwowen/p/11178012.html
Copyright © 2011-2022 走看看