单调栈和单调队列

最近我发现很多人不知道单调栈是什么,单调队列是什么,比如我的队友,所以我想统一讲一下单调栈和单调队列。但是即使我知道单调栈和单调队列,由于洛谷上面没有单调栈专题,我是没有练习过单调栈的,导致我在比赛中十分生疏,失误连连,最后痛失ICPC银牌,我因为这个几天都没睡好,像是得了银川+单调栈PTSD,一听到这两个词就难受。

定义:

单调栈和单调队列与其说是一种数据结构,我更认为他们是一种算法思想。

单调栈:维护一个保持栈的单调性

单调队列:维护一个队列的单调性

作用:

两者名字相似,作用完全不同

单调栈:找向左,向右第一个比当前元素小(大)的元素。O(n)算法。

单调队列:私以为单调队列就是滑动窗口,大部分是定长的滑动窗口,也有些变长的滑动窗口,那些变长的滑动窗口其实更像是尺取的一种思想。滑动窗口,顾名思义,就是在一个数组结构中滑动的窗口,作用,维护在数组中的一个定长的窗口的最大值(最小值),这里听不懂窗口可以直接看例题。O(n)算法。

例题:

单调栈:https://www.luogu.org/problem/P2659

题目背景

GD是一个热衷于寻求美好事物的人,一天他拿到了一个美丽的序列。

题目描述

为了研究这个序列的美丽程度,GD定义了一个序列的“美丽度”和“美丽系数”:对于这个序列的任意一个区间[l,r],这个区间的“美丽度”就是这个区间的长度与这个区间的最小值的乘积,而整个序列的“美丽系数”就是它的所有区间的“美丽度”的最大值。现在GD想要你帮忙计算这个序列的“美丽系数”。

输入格式

第一行一个整数n,代表序列中的元素个数。 第二行n个整数a1、a2„an,描述这个序列。

输出格式

一行一个整数,代表这个序列的“美丽系数”。

输入输出样例

输入 #1

3 
1 2 3

输出 #1

4

说明/提示

样例解释 选取区间[2,3],可以获得最大“美丽系数”为2*2=4。 数据范围 对于20%的数据,n<=2000; 对于60%的数据,n<=200000; 对于100%的数据,1<=n<=2000000,0<=ai<=2000000。 提示 你可能需要一个读入优化。

思路:单调栈裸题,下图就是单调栈能处理的一般问题

单调栈和单调队列_第1张图片

图是用win10的画图软件画的,图与题目样例无关,画的不好,凑合看。上面的题可以建模成这样的图,然后找一个最大矩形,大概就是红色或者黄色矩阵,在学习单调栈之前,我们做这种题只能用O(n^2)的方法,而离散处理的话,只要出题人卡一手离散数据就还是O(n^2)。

使用单调栈,我们只需向左向右分别维护一个单调栈,找出每个点的左端点和右端点,然后再一遍O(n)求出最大值max{a[i]*(R[i]-L[i])},a[i]是高度,R[i]是向右第一个比当前点高度小的点,L[i]是向左第一个比当前点高度小的点。本题的点不是连续的,是离散的,所以结果是max{a[i]*(R[i]-L[i]-1)}。

题解代码有很多,直接在洛谷里面就能看,我这边给出一个我的代码风格的单调栈模板,具体算法思想见代码及注释。

int L[maxn],R[maxn],a[maxn];//L-左边第一个比当前元素小的元素的位置,R同理
int S[maxn];//栈
int n;//数组大小
void monoStack(){
	int top=0;//栈顶
	for(int i=1;i<=n;i++){
		while(top>0&&a[i]<=a[S[top]])top--;//弹出所有不小于当前数的数
		L[i]=S[top];//记录左边第一个比这个数小的数,栈空就是0
		S[++top]=i;//将当前位置入栈
	}
	top=0;
	S[0]=n+1;
	for(int i=n;i>=1;i--){//和上面同理,不写了
		while(top>0&&a[i]<=a[S[top]])top--;
		R[i]=S[top];
		S[++top]=i;
	}
}

单调队列:https://www.luogu.org/problem/P1886

题目描述

现在有一堆数字共N个数字(N<=10^6),以及一个大小为k的窗口。现在这个从左边开始向右滑动,每次滑动一个单位,求出每次滑动后窗口中的最大值和最小值。

例如:

The array is [1 3 -1 -3 5 3 6 7], and k = 3.

单调栈和单调队列_第2张图片

输入格式

输入一共有两行,第一行为n,k。

第二行为n个数(

输出格式

输出共两行,第一行为每次窗口滑动的最小值

第二行为每次窗口滑动的最大值

输入输出样例

输入 #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%的数据,n<=10^5

100%的数据,n<=10^6

思路:单调队列的作用就是维护一个序列的一个窗口的最小值(最大值),因此,本题是纯裸题。

这边给出一个我自己写习惯的单调队列,目前还没出过BUG,详细算法见代码及注释。最小值同理,稍微改一下出队条件即可。

int a[maxn];
int n,k;//n为数组长度,k为窗口长度
int getMax(){//fi和se被宏定义为first和second
	deque >q;//核心数据结构为双端队列
	for(int i=1;i<=k;i++){
		if(q.empty()||a[i]>=q.front().fi)q.clear();//有一个数比队列头大,全队列都没这个数有用
		else while(q.back().fi<=a[i])q.pop_back();//这个数比所有队列后面比这个数小的数有用
		q.push_back(make_pair(a[i],i));
	}
	int res=q.front().fi;
	for(int i=k+1;i<=n;i++){
		while(q.front().se<=i-k)q.pop_front();//若队列头的序号在窗口外,将其弹出
		if(a[i]>=q.front().fi)q.clear();
		else while(q.back().fi<=a[i])q.pop_back();
		q.push_back(make_pair(a[i],i));
		res=max(q.front().fi,res);//不断更新最大值,队列头就是维护的窗口最大值
	}
	return res;
}

 

你可能感兴趣的:(算法专题)