单调队列(stl)

单调队列是一种队列内的元素有单调性(单调递增或者单调递减)的队列,答案(也就是最优解)就存在队首,而队尾则是最后进队的元素。因为其单调性所以经常会被用来维护区间最值或者降低DP的维数已达到降维来减少空间及时间的目的。
那么怎么判断这个数是否还在这个区间之内呢?

这时候就要用到一个结构体了

struct node{
	int val;
	int pos;
}a[MAXN];

val存储该点的值,pos存储该点的位置,就是为了能够在循环中判断能否把这个点踢掉
根据单调队列的性质,如果我们想要将一个新的点插入。若是递减的单调队列则需要判断队列尾的元素是否大于要插入的元素,若大于,则踢掉队尾的元素,直到找到不大于队尾元素退出循环,并且插入元素,代码为:

while(!queue.empty()&&queue.back().val<=a[i].val)
			queue.pop_back();
		queue.push_back(a[i]);

若是递增的单调队列则需要判断队列尾的元素是否小于要插入的元素,若小于,则踢掉队尾的元素,直到找到不小于队尾元素退出循环,并且插入元素,代码为:

while(!q.empty()&&q.back().val>=a[i].val)
			q.pop_back();
		q.push_back(a[i]);

那么对于已经过期的元素怎么办呢?
如果一个元素从队首开始踢人,如果队首没有过期 其他的元素你管他干嘛?又不是答案,现在又用不到;如果队首过期,那就踢掉他继续踢下一个,直到找到一个没有过期的,代码为:

while(!q.empty()&&q.front().pos<i-m)
			q.pop_front();

下面是三个洛谷上的单调队列的练习模板例题:
(1)P1440

https://www.luogu.com.cn/problem/P1440

题目描述
一个含有 n 项的数列,求出每一项前的 m 个数到它这个区间内的最小值。若前面的数不足 m 项则从第 1 个数开始,若前面没有数则输出 0。

输入格式
第一行两个整数,分别表示 n,m。

第二行,n 个正整数,为所给定的数列 ai。

输出格式
n 行,每行一个整数,第 i 个数为序列中 ai之前 m 个数的最小值。

输入输出样例
输入 #1
6 2
7 8 1 4 3 2
输出 #1
0
7
7
1
1
3
说明/提示
对于 100% 的数据,保证 1<=m<=n<=2*106,1<=ai<=3×107
模板题,用到了递减的单调队列。
输入输出时要用 scanf 和 printf ,或者写快读函数,若使用 cin 和 cout 会TLE。
AC代码如下:

#include<iostream>
#include<cstdio>
#include<queue>
using namespace std;
typedef long long ll;
const int MAXN=2e6+5;
struct node
{
	int val;
	int pos;
}a[MAXN];
deque<node>q;
int qq[MAXN];
int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i].val);
		a[i].pos=i-1;
	}
	qq[0]=0;
	for(int i=1;i<=n;i++)
	{
		while(!q.empty()&&q.back().val>=a[i].val)
			q.pop_back();
		q.push_back(a[i]);
		while(!q.empty()&&q.front().pos<i-m)
			q.pop_front();
		qq[i]=q.front().val;
	}
	for(int i=0;i<n;i++)
		printf("%d\n",qq[i]);
	return 0;
} 

(2)P2032

https://www.luogu.com.cn/problem/P2032

题目描述
有一个 1 ×n 的矩阵,有 n 个整数。现在给你一个可以盖住连续 k 个数的木板。一开始木板盖住了矩阵的第 1∼k 个数,每次将木板向右移动一个单位,直到右端与第 n 个数重合。每次移动前输出被覆盖住的数字中最大的数是多少。

输入格式
第一行两个整数 n,k表示共有 n 个数,木板可以盖住 k 个数。
第二行 n 个整数,表示矩阵中的元素。

输出格式
共 n - k + 1 行,每行一个整数。

第 i 行表示第 i∼i+k−1 个数中最大值是多少。

输入输出样例
输入 #1
5 3
1 5 3 4 2
输出 #1
5
5
4
说明/提示
对于20% 的数据,1<= k <= n <= 103
对于 50% 的数据,1 <= k <= n <= 104
对于100% 的数据,1 <= k <= n <=2*106
,矩阵中的元素大小不超过 104 并且均为正整数。

模板题,用到了递增的单调队列,AC代码如下:

#include<iostream>
#include<cstdio>
#include<deque>
using namespace std;
const int MAXN=2e6+10;
int n,k;
struct node{
	int val;
	int pos;
}a[MAXN];
deque<node>queue;
int main()
{
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i].val);
		a[i].pos=i;
	}
	for(int i=1;i<=n;i++)
	{
		while(!queue.empty()&&queue.front().pos<i-k+1)
				queue.pop_front();
		while(!queue.empty()&&queue.back().val<=a[i].val)
			queue.pop_back();
		queue.push_back(a[i]);
		if(i>=k)
			cout<<queue.front().val<<endl;
	}
	return 0;
}

(3)P1886

https://www.luogu.com.cn/problem/P1886

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

The array is [1,3,-1,-3,5,3,6,7], and k = 3。
![在这里插入图片描述](https://img-blog.csdnimg.cn/0429370de5de46d7ab4a99f56ca56f41.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ2MDAwNTg0,size_16,color_FFFFFF,t_70
输入格式
输入一共有两行,第一行有两个正整数 n,k。第二行 n 个整数,表示序列 a
输出格式
输出共两行,第一行为每次窗口滑动的最小值
第二行为每次窗口滑动的最大值

输入输出样例
输入 #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 <= n <= 105;
对于 100% 的数据,1<= k <= n <= 106 ,ai∈[−231,231)。
模板题,同时用到了递减和递增的单调队列,就是把前面两个题目的插入删除部分合起来,结果放到两个数组中输出,或者分两部分写就可以了,231的数据大约为1e9左右,不会爆掉int,不需要开long long,AC代码如下:

#include<iostream>
#include<cstdio>
#include<deque>

using namespace std;
typedef long long ll;
const int MAXN=1e6+10;
int n,k;
struct node{
	int val;
	int pos;
}a[MAXN];
deque<node>maxq,minq;
int max_v[MAXN],min_v[MAXN];
int main()
{
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i].val);
		a[i].pos=i;
	}
	int tmpmax=0,tmpmin=0;
	for(int i=1;i<=n;i++)
	{
		while(!maxq.empty()&&maxq.front().pos<i-k+1)
			maxq.pop_front();
		while(!maxq.empty()&&maxq.back().val<=a[i].val)
			maxq.pop_back();
		maxq.push_back(a[i]);
		
		while(!minq.empty()&&minq.front().pos<i-k+1)
			minq.pop_front();
		while(!minq.empty()&&minq.back().val>=a[i].val)
			minq.pop_back();
		minq.push_back(a[i]);
		if(i>=k)
		{
			max_v[tmpmax++]=maxq.front().val;
			min_v[tmpmin++]=minq.front().val;
		}
	}
	for(int i=0;i<tmpmin;i++)
		cout<<min_v[i]<<" ";
	cout<<endl;
	for(int i=0;i<tmpmax;i++)
		cout<<max_v[i]<<" ";
	return 0;
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值