2024/1/26单调队列

单调队列

<1>例题:P1886 滑动窗口 /【模板】单调队列

有一个长为 n 的序列 a,以及一个大小为 k 的窗口。现在这个从左边开始向右滑动,每次滑动一个单位,求出每次滑动后窗口中的最大值和最小值

对于该题,需要我们在每一次窗口滑动时得出窗口中的最大值和最小值,可以将其分开求,求一次最大值和一次最小值并分开输出即可,但怎么输出呢?那便需要我们的单调队列登场了;

单调队列

首先什么是单调队列?单调队列即队列里每一个元素都严格按照单调排列,即单调递增或单调递减,并在每一次增加或减少队列元素时都保持单调,且队首为最大元素或最小元素(具体看队列是单调递增还是单调递减)

例如:有8个元素 5,4,6,7,8,3,9,10;要求我们组成一个单调递增的队列

会形成这样一个队列:3,9,10;

按步骤一步一步则为

  1. 5
  2. 4
  3. 4,6
  4. 4,6,7
  5. 4,6,7,8
  6. 3
  7. 3,9
  8. 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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值