单调队列
单调队列
定义
单调队列是指通过对队列的队首和队尾进行操作以保证队列的单调性
使用
单调队列可以用来查询区间中的最大和最小值,例如下面这道模板题
P1886 滑动窗口 /【模板】单调队列
题目描述
有一个长为 \(n\) 的序列 \(a\),以及一个大小为 \(k\) 的窗口。现在这个窗口从左边开始向右滑动,每次滑动一个单位,求出每次滑动后窗口中的最小值和最大值。
例如,对于序列 \([1,3,-1,-3,5,3,6,7]\) 以及 \(k = 3\),有如下过程:
\[\def\arraystretch{1.2}
\begin{array}{|c|c|c|}\hline
\textsf{窗口位置} & \textsf{最小值} & \textsf{最大值} \\ \hline
\verb![1 3 -1] -3 5 3 6 7 ! & -1 & 3 \\ \hline
\verb! 1 [3 -1 -3] 5 3 6 7 ! & -3 & 3 \\ \hline
\verb! 1 3 [-1 -3 5] 3 6 7 ! & -3 & 5 \\ \hline
\verb! 1 3 -1 [-3 5 3] 6 7 ! & -3 & 5 \\ \hline
\verb! 1 3 -1 -3 [5 3 6] 7 ! & 3 & 6 \\ \hline
\verb! 1 3 -1 -3 5 [3 6 7]! & 3 & 7 \\ \hline
\end{array}
\]
输入格式
输入一共有两行,第一行有两个正整数 \(n,k\);
第二行有 \(n\) 个整数,表示序列 \(a\)。
输出格式
输出共两行,第一行为每次窗口滑动的最小值;
第二行为每次窗口滑动的最大值。
输入输出样例 #1
输入 #1
8 3
1 3 -1 -3 5 3 6 7
输出 #1
-1 -3 -3 -3 3 3
3 3 5 5 6 7
说明/提示
【数据范围】
对于 \(50\%\) 的数据,\(1 \le n \le 10^5\);
对于 \(100\%\) 的数据,\(1\le k \le n \le 10^6\),\(a_i \in [-2^{31},2^{31})\)。
思考
通过解读题目意思可以知道,题目要求我们求一个长度为k的窗口中的最大值和最小值,如果采用枚举的方法,时间复杂度为(n^2)肯定会超时,所以这个时候我们采用单调队列的方式来优化。
假如我们现在要进入一个新的值x,我们保证队列为单调递减,那么我们就得从队尾删除所有大于x的数,然后从队尾加入x,如果我们的队列长度大于窗口长度,那么我们就得从队首依次踢出元素,直到队列长度小于窗口长度,那么这样的话我们就可以保证我们队列的对首一定是我们这一段窗口的最大值。
伪代码
i64 head = 1 , tail = 1;//定义队伍的队首和队尾
for (int i = 1; i <= n; i++) {
if (i - idx [ head ] >= k)//idx表示队首的下标,如果长度大于k,那么把他踢出队列
head++;
while (head <= tail && maxx [ tail ]> num [ i ]) {//保证单调递增性,踢出所有比他大的
tail--;
}
maxx [ ++tail ] = num[i];//新元素入队
idx [ tail ] = i;//记录下标
if (i >= k)
cout << maxx [ head ] << " ";
}
AC代码
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
using i64 = long long;
i64 num [ 1000005 ] , minn [ 1000005 ] , maxx [ 1000005 ];
i64 idx [ 1000005 ];
i64 n , k;
int main() {
cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> num [ i ];
}
i64 head = 1 , tail = 1;//定义队伍的队首和队尾
for (int i = 1; i <= n; i++) {
if (i - idx [ head ] >= k)//idx表示队首的下标,如果长度大于k,那么把他踢出队列
head++;
while (head <= tail && maxx [ tail ]> num [ i ]) {//保证单调递增性,踢出所有比他大的
tail--;
}
maxx [ ++tail ] = num[i];//新元素入队
idx [ tail ] = i;//记录下标
if (i >= k)
cout << maxx [ head ] << " ";
}
cout << endl;
head = 1 , tail = 1;//定义队伍的队首和队尾
for (int i = 1; i <= n; i++) {
if (i - idx [ head ] >= k)//idx表示队首的下标,如果长度大于k,那么把他踢出队列
head++;
while (head <= tail && minn [ tail ] < num [ i ]) {//保证单调递减性,踢出所有比他小的
tail--;
}
minn [ ++tail ] = num[i];//新元素入队
idx [ tail ] = i;//记录下标
if (i >= k)
cout << minn [ head ] << " ";
}
}

浙公网安备 33010602011771号