单调队列入门

本篇博客转自我很久以前在洛谷上写的一篇博客,原地址: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.

688.png

输入格式:

输入一共有两行,第一行为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("\n");
    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("\n");
    return 0;
}

应用

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

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

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

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

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

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

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

感谢阅读!

转载于:https://www.cnblogs.com/ybwowen/p/11178012.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值