首页 > 技术文章 > 洛谷 P1886 滑动窗口 /【模板】单调队列

57xmz 2020-11-04 21:22 原文

题目传送门

普及算法都不会,参加个锤子CSP—S

思路

完全剽窃别人的思路,话说回来,我要是会还学它干啥。单调队列,顾名思义,队列里的元素都是有单调性的,通过维护单调性,优化时间复杂度。干说其实根本没用,直接看题。首先根据题解的思路,求最小值的时候将队列内元素从小到大排列,这样每次查询最小值的时候,只需要弹出队首即可。然后我们考虑怎么样维护单调队列。当来了一个新数,我们就从后往前找(就是从大到小),如果这个新数比队列中的那些元素都大,那么那些大的元素就已经不可能作为最小值的候选人了,所以直接弹出队列即可。每次循环还要检查一遍队列的跨度,如果队首和当前的序号相差过远了,那么就只好忍痛割爱,弹出最小的元素了。不难发现,普通的队列是无法实现单调队列的(我不会告诉你我想了一个小时才发现了这个道理),因为它既要弹出队首元素也要弹出队尾元素,这就需要手写一个双端队列来实现。其实并不难写,就是队尾也可以弹出元素罢了。但是代码实现还是有一些细节的。

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<ctime>
#include<cstring>
#include<queue>
using namespace std;
typedef long long ll;
int n,m;
ll a[1000005];
int q[1000005],p[1000005];
void _minn(){
	int head=1,tail=0;//这样初始化,是因为队列内一开始是空的,而队列为空时就相当于是head>tail
	for(int i=1;i<=n;i++){
		while(head<=tail&&q[tail]>=a[i]){//维护单调性
			tail--;
		}
		q[++tail]=a[i];
		p[tail]=i;
		if(p[head]<=i-m) head++;//“窗口”长度过长,被迫抛弃最小元素(如果将这一行加到while循环前面的话,则需要加上head<=tail这一个条件。否则的话若区间长度为1,则第一次循环后head=2,tail=0,喜提RE)
		if(i>=m) printf("%lld ",a[p[head]]);
	}
	printf("\n");
}
void _maxx(){
	int head=1,tail=0;
	for(int i=1;i<=n;i++){
		while(head<=tail&&q[tail]<=a[i]){
			tail--;
		}
		q[++tail]=a[i];
		p[tail]=i;
		if(p[head]<=i-m) head++;
		if(i>=m) printf("%lld ",a[p[head]]);
	}
	printf("\n");
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%lld",&a[i]);
	}
	_minn();
	_maxx();
	return 0;
}

推荐阅读