传送门 | 难度 |
---|---|
https://www.luogu.com.cn/problem/P1886 | 普及/提高- |
如题,单调队列板子题。
单调队列是一个双端队列,头和尾都可以出元素。
本文对单调队列的实现进行简述、模拟,方便理解,然后给出完整AC代码
符号说明
- n:序列a的长度
- k:窗口的长度
- h:队首
- t:队尾
- a[]:存储序列a的数组
- q[]:单调队列实现数组,存储的是序列a数组的下标index(index∈[0 , n-1]且index∈N)
分析
因为求最大值与求最小值的思路是基本相同的,所以以求最小值为例进行分析,即对void minqueue()
进行分析。
- 首先是队头和队尾
int h = 0, t = -1;//队首h,队尾t
这就是对队列进行模拟所采用的头尾。当队列为空时,总有h-t == 1
成立。
- 接着就是最核心的两个
while
循环和一步数组赋值q[++t] = i
操作。- 第一个while主要是要保证单调队列的窗口长度不能大于所给的窗口长度
- 第二个while主要是依据所给的减(增等条件)去除明显不可能的结果。
此处给出一个典型用例的模拟,看完就清楚了
典型用例模拟分析
6 3
1 3 -1 3 6 7
这个用例的特点是,index=2的元素-1比前面两个都小,而index=2,3,4,5,的这四个元素呈现递增。
典型用例模拟分析表格(minqueue()运行过程)
第id步 | i | h | t | q |
---|---|---|---|---|
1(初始化状态) | - | 0 | -1 | {} |
2 | 0 | 0 | -1→0 | {0} |
3 | 1 | 0 | 0→1 | {0,1} |
4 | 2 | 0 | 1→0→-1→0 | {2} |
5 | 3 | 0 | 0→1 | {2,3} |
6 | 4 | 0 | 1→2 | {2,3,4} |
7 | 5 | 0→1 | 2→3 | {3,4,5} |
id = 4 那一行就是第二个while的效果
id = 7 那一行就是第一个while的效果
AC代码
#include<iostream>
#include<cstdio>
using namespace std;
int a[1000005];//序列
int n, k;//序列长度n,窗口长度k
int q[1000005];//模拟队列的数组
void minqueue() {//求最小值
int h = 0, t = -1;//队首h,队尾t
for (int i = 0; i < n; ++i) {
while (h <= t && q[h] + k <= i)
h++;
while (h <= t && a[i] < a[q[t]])
t--;
q[++t] = i;
if (i >= k - 1) {
printf("%d", a[q[h]]);
if (i != n - 1)
printf(" ");
}
}
}
void maxqueue() {//求最大值
int h = 0, t = -1;//队首h,队尾t
for (int i = 0; i < n; ++i) {
while (h <= t && q[h] + k <= i)
h++;
while (h <= t && a[i] > a[q[t]])
t--;
q[++t] = i;
if (i >= k - 1) {
printf("%d", a[q[h]]);
if (i != n - 1)
printf(" ");
}
}
}
int main() {
scanf("%d%d", &n, &k);
for (int i = 0; i < n; ++i)
scanf("%d", &a[i]);
minqueue();
printf("\n");
maxqueue();
return 0;
}