单调队列
<1>例题:P1886 滑动窗口 /【模板】单调队列
有一个长为 n 的序列 a,以及一个大小为 k 的窗口。现在这个从左边开始向右滑动,每次滑动一个单位,求出每次滑动后窗口中的最大值和最小值
对于该题,需要我们在每一次窗口滑动时得出窗口中的最大值和最小值,可以将其分开求,求一次最大值和一次最小值并分开输出即可,但怎么输出呢?那便需要我们的单调队列登场了;
单调队列
首先什么是单调队列?单调队列即队列里每一个元素都严格按照单调排列,即单调递增或单调递减,并在每一次增加或减少队列元素时都保持单调,且队首为最大元素或最小元素(具体看队列是单调递增还是单调递减)
例如:有8个元素 5,4,6,7,8,3,9,10;要求我们组成一个单调递增的队列
会形成这样一个队列:3,9,10;
按步骤一步一步则为
- 5
- 4
- 4,6
- 4,6,7
- 4,6,7,8
- 3
- 3,9
- 3,9,10
可能看一遍也是看不明白,再解释一遍
首先开始队列为空,没有元素,则将第一个元素5压入,接着加入4,但是要求为单调递增,而4小于前一个元素5,则将5弹出,将4压入,然后第三个元素6比4大,直接压入;7比6大直接压入,8比7大直接压入,然后后一个元素3比8小,则将8弹出,弹出后7任然比3大,再将7弹出,直到前面的元素都比3小时将3压入,后面要压入的元素9比3大,压入,10比9大,压入,完成
所以得到了一个这样的队列;
所以回到题目,我们该怎么解决呢?
可以使用数组来模拟队列,或是使用c++自带的queue队列
下面是使用数组进行模拟:
首先需要定义队列首尾的下标,分别定义为head,back
由于我在函数中使用的for循环i是从1开始,所以将head定义为1,back定义为0
对于队列需要实现以下功能(按单调递增来讲):
对队首的检查:检查队首是否在窗口【i-k+1,i】这个区间内,即队首元素的下标是否指向i-k,不是则需要将队首元素弹出,使head++;
对队尾的检查:即每次检查,将队尾元素与待压入元素进行比较,如果队尾元素比待压入元素大,则将队尾元素弹出,使back--;
将待压入元素加入队尾:这里定义两个数组a[],q[];分别表示输入的一串序列和队列,
当满足可以加入队尾的条件时,q[++back]=a[i];
代码实现如下:(此为单调递增队列)
int head=1;int back=0;//定义队首,队尾
for(int i=1;i<=n;i++)
{
while(head<=back&&q[back]>=a[i])//队列不为空,当对尾元素比要压入元素大时,将队尾元素弹出
{
back--;//弹出
}
q[++back]=a[i];//前面的元素比当前要压入元素小时,压入,形成单调区间
p[back]=i;//记录窗口滑动
while(p[head]<=i-k)//窗口滑动,如果没有滑动到最后
{
head++;//滑动
}
if(i>=k)//每滑动一次窗口
{
cout<<q[head]<<" ";//输出队头
}
}
cout<<endl;
则在这样队列中,队首永远会是最小元素,只需要每次输出队首即可(单调递增队列)
那么题目就好解决了
完整代码如下:
#include<bits/stdc++.h>
using namespace std;
struct aqueue//定义结构体
{
int n,k,a[1000005];
int q[1000005],head,back,p[1000005];//队首队尾定义
void read()
{
cin>>n>>k;
for(int i=1;i<=n;i++)
{
cin>>a[i];//读入数据
}
}
void findmax()//最大值
{
head=1;back=0;
for(int i=1;i<=n;i++)
{
while(head<=back&&q[back]<=a[i])//队列不为空,当对尾元素比要压入元素小时,将队尾元素弹出
{
back--;//弹出
}
q[++back]=a[i];//前面的元素比当前要压入元素大时,压入,形成单调区间
p[back]=i;//记录窗口滑动
while(p[head]<=i-k)//窗口滑动,如果没有滑动到最后
{
head++;//滑动
}
if(i>=k)//每滑动一次窗口
{
cout<<q[head]<<" ";//输出队头
}
}
cout<<endl;
}
void findmin()//最小值
{
head=1;back=0;
for(int i=1;i<=n;i++)
{
while(head<=back&&q[back]>=a[i])//队列不为空,当对尾元素比要压入元素大时,将队尾元素弹出
{
back--;//弹出
}
q[++back]=a[i];//前面的元素比当前要压入元素小时,压入,形成单调区间
p[back]=i;//记录窗口滑动
while(p[head]<=i-k)//窗口滑动,如果没有滑动到最后
{
head++;//滑动
}
if(i>=k)//每滑动一次窗口
{
cout<<q[head]<<" ";//输出队头
}
}
cout<<endl;
}
}deal;
int main()
{
deal.read();
deal.findmin();
deal.findmax();
return 0;
}