数据结构之单调队列与单调栈

数据结构之单调队列与单调栈

目录

一:单调队列

1:单调队列的概念

2:单调队列的模拟

3:单调队列的实现

4.单调队列的应用

                     holiday

5.补充与说明

单调队列例题:

二:单调栈

1.单调栈的基本概念与实现

扫描二维码关注公众号,回复: 13129951 查看本文章

2.单调栈的例题


一:单调队列

我们还是先放上一道题

洛谷P1886 滑动窗口

题目描述

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

例如:

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

输入格式

输入一共有两行,第一行有两个正整数 n,k。 第二行 n 个整数,表示序列 a

输出格式

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

输入输出样例

输入 

8 3
1 3 -1 -3 5 3 6 7

输出 

-1 -3 -3 -3 3 3
3 3 5 5 6 7

说明/提示

【数据范围】
对于 50% 的数据,1≤n≤105;
对于 100% 的数据,1≤k≤n≤1e6,ai​∈[−231,231)。

这道题依然有很多方法

方法一:暴力枚举,显然(真的很显然)会爆

方法二:线段树维护区间最值,是可以,但是实现复杂,且常数较大

方法三:st表,同线段树常数大

方法四:单调队列

不知道单调队列是什么?没事,看完这篇,你肯定会了。

1:单调队列的概念

单调队列,顾名思义,首先是一个队列,其次,它具有一定的单调性

怎么单调呢?

1:其内部的存编号(数组下表)是具有单调性(一般是递增)

2:队列存的值具有单调性(单调递增或单调递减);

2:单调队列的模拟

就上题而言

假如说我们需要维护每一个固定长度区间的最小值

上图理解(图丑请见谅)

首先这个窗口在1~3区间

i=1,此时内队列为空,所以1无条件入队

i=2,此时队列有个1,3比1大,所以也入队

i=3,此时队尾是3,-1来了,一看,哎呦,我比你小,你凭什么站到我前面

于是-1把3踢出了队列,此时队列里是1,-1

然后-1又发现,哎呦,前面怎么还有一个比我大的家伙,于是它又把1踢出队列

所以此时队首元素是-1,所以区间最小值是-1

然后区间往后移,2~4

i=4,-3来了,他发现-1比他大,就把-1踢出去,自己站在了第一

区间最小值为队首元素-3

然后区间继续往右移

i=5,5比-3大,所以它默默的排在了后面

区间最小值仍是-3

区间继续右移

i=6,3比5小于是代替了5

此时区间最小值还是-3

然后区间继续往右

i=7,6来了,他发现,哎,你-3都已经不在这个区间里了,凭什么还在这

于是-3被撵了出去,现在队首元素是3

所以区间最小值是-3

区间继续右移

i=8,7跑过来了,他看前面的都比他小,于是就站在了队尾

此时队首是3,所以最小值是3

所以输出序列为:-1 -3 -3 -3 3 3

这就是单调队列的模拟过程,它是O(n)的优秀复杂度

其中心思想就一个:

你在我前面,还没有我小,要你何用?

然后每一步的队首元素就是最小值

如果队首越界,就弹出队首。

我相信,你应该能听懂了

不用着急实现,一步一步,慢慢来。

3:单调队列的实现

你应该已经发现了,这个队列需要支持队首弹出,队尾插入和队尾弹出???

怎么实现呢?

STL大法好

在STL的容器里,有一个队列容器叫做deque(双向队列)

它可以支持队首队尾都能插入,弹出

基本操作如下

q.push_front()      队首插入

q.push_back()      队尾插入

q.pop_front()         队首弹出

q.pop_back()        队尾弹出

q.front()        返回队首元素

q.back()         返回队尾元素

q.size()和q.empty()        不多说了

所以我们的代码就诞生了!

#include<bits/stdc++.h>
using namespace std;
struct node
{
	int id;	//存的编号,判断越界 
	int val;
};
deque<node> q1;		//最小值队列 
deque<node> q2;		//最大值队列 
int x,k,n;
int a1[1000020],a2[1000020],top1,top2;
int main()
{
	cin>>n>>k;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&x);
		while(!q1.empty()&&x<=q1.back().val)
		q1.pop_back();	//弹出队尾元素 
		while(!q2.empty()&&x>=q2.back().val)
		q2.pop_back();		
		q1.push_back(node{i,x}); //压入队列 
		q2.push_back(node{i,x});
		if(i-k+1>q1.front().id)
		q1.pop_front();	//如果队首越界,弹出 
		if(i-k+1>q2.front().id)
		q2.pop_front();
		if(i>=k)
		{
			a1[++top1]=q1.front().val;	//答案数组 
			a2[++top2]=q2.front().val;
		} 
	}
	for(int i=1;i<top1;i++)
	cout<<a1[i]<<' ';
	cout<<a1[top1];
	cout<<endl;
	for(int i=1;i<top2;i++)
	cout<<a2[i]<<' ';
	cout<<a2[top2];
	return 0;
}

怎么样懂了吗

上题

4.单调队列的应用

holiday

题目描述

经过几个月辛勤的工作,FJ决定让奶牛放假。

假期可以在1…N天内任意选择一段(需要连续),每一天都有一个享受指数W。但是奶牛的要求非常苛刻,假期不能短于P天,否则奶牛不能得到足够的休息;假期也不能超过Q天,否则奶牛会玩的腻烦。

FJ想知道奶牛们能获得的最大享受指数。

输入格式

第一行:N,P,Q.

第二行:N个数字,中间用一个空格隔开。

输出格式

一个整数,奶牛们能获得的最大享受指数。

样例数据

input

5 2 4
-9 -4 -3 8 -6

output

5

Hint 选择第3-4天,享受指数为-3+8=5。

数据规模与约定

50% 1≤N≤10000

100% 1≤N≤100000

时间限制:1s

空间限制:256MB

用前缀和处理前i天的指数和

其实就是从P的位置开始枚举,每次把i-P压入队列,如果i-Q大于队首元素的位置,弹出队首

每次取出队首让ans=max(sum[i]-sum[Day.front()])

最后输出ans

#include<bits/stdc++.h>
using namespace std;
#define INF 9999999999
long long sum[100300];
int n,p,q;
long long ans=-INF;
deque<long long> Day;
int main()
{
	freopen("holiday.in","r",stdin);
	freopen("holiday.out","w",stdout);
	scanf("%d%d%d",&n,&p,&q);
	long long x;
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&x);
		sum[i]=sum[i-1]+x;
	}
	for(int i=p;i<=n;i++)
	{
		
		while(!Day.empty()&&sum[Day.back()]>sum[i-p])
		Day.pop_back();
		Day.push_back(i-p);
		while(!Day.empty()&&i-q>Day.front()) 
		Day.pop_front();
		ans=max(ans,sum[i]-sum[Day.front()]);
	}
	cout<<ans;
	return 0;
}

5.补充与说明

单调队列对于处理线性滑动区间最值可谓游刃有余

故此,有一部分动态规划题可用单调队列进行优化

这些我们以后再讨论

单调队列例题:

P1440 求m区间内的最小值

P1714 切蛋糕

P2032 扫描

P2216 [HAOI2007]理想的正方形

P2564 [SCOI2009]生日礼物

二:单调栈

有了单调队列的基础,你应该能体会到具有单调性的数据结构是什么样的了

1.单调栈的基本概念与实现

单调栈,和单调栈一样,是具有单调性的栈

但有一点,单调递增(或递减)是指栈内元素的出栈序列递增(或递减),而栈内元素是递减(或递增)的

并且,没有双向栈(。。。。。。)

我们同样也找一道例题

洛谷P2866 [USACO06NOV]Bad Hair Day S

题目描述

Some of Farmer John's N cows (1 ≤ N ≤ 80,000) are having a bad hair day! Since each cow is self-conscious about her messy hairstyle, FJ wants to count the number of other cows that can see the top of other cows' heads.

Each cow i has a specified height hi (1 ≤ hi ≤ 1,000,000,000) and is standing in a line of cows all facing east (to the right in our diagrams). Therefore, cow i can see the tops of the heads of cows in front of her (namely cows i+1, i+2, and so on), for as long as these cows are strictly shorter than cow i.

Consider this example:

=

=       =

=   -   =         Cows facing right -->

=   =   =

= - = = =

= = = = = =

1 2 3 4 5 6 Cow#1 can see the hairstyle of cows #2, 3, 4

Cow#2 can see no cow's hairstyle

Cow#3 can see the hairstyle of cow #4

Cow#4 can see no cow's hairstyle

Cow#5 can see the hairstyle of cow 6

Cow#6 can see no cows at all!

Let ci denote the number of cows whose hairstyle is visible from cow i; please compute the sum of c1 through cN.For this example, the desired is answer 3 + 0 + 1 + 0 + 1 + 0 = 5.

输入格式

Line 1: The number of cows, N.

Lines 2..N+1: Line i+1 contains a single integer that is the height of cow i.

输出格式

Line 1: A single integer that is the sum of c1 through cN.

题意翻译

农夫约翰有N (N≤80000)头奶牛正在过乱头发节。每一头牛都站在同一排面朝东方,而且每一头牛的身高为hi​。第N头牛在最前面,而第1头牛在最后面。 对于第i头牛前面的第j头牛,如果hi​>hi+1​并且hi​>hi+2​ ⋯hi​>hj​,那么认为第i头牛可以看到第i+1到第j头牛

定义Ci​为第ii头牛所能看到的别的牛的头发的数量。请帮助农夫约翰求出所有Ci的和

输入输出样例

输入 #1复制

6
10
3
7
4
12
2

输出 #1复制

5

废话不多说,直接看正解

我们可以维护一个单调递增的栈(栈内单调递减)

每来一个新的奶牛,如果它比栈顶小,直接入栈,因为他对前面的奶牛造不成视觉障碍(他是最低的嘛)

如果他比栈顶大于等于,就一直弹出栈顶,直到他比栈顶小,并每次用这个新来的奶牛的位置减去栈顶的位置然后+1,并累加。

注意

可能遍历完后栈内依然有一个单调递减的序列,我们以此将他弹出,每次累加(n-栈顶元素的位置+1);

话不多说,上代码

#include<bits/stdc++.h>
using namespace std;
struct node
{
	int id;
	int val;
};
int n,x;
long long ans=0;
stack<node> st;
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&x);
		while(!st.empty()&&x>=st.top().val)
		{			
			ans+=1ll*(i-st.top().id-1);
			st.pop();
		}
		st.push(node{i,x});
	}
	while(!st.empty())
	{
		int t=st.top().id;
		st.pop();
		ans+=1ll*(n-t);
	}
	cout<<ans;
	return 0;
}

2.单调栈的例题

P5788 【模板】单调栈

P6198 [EER1]单调栈

POJ 2796

POJ 2559

你听懂了吗?

猜你喜欢

转载自blog.csdn.net/jwg2732/article/details/107879262
今日推荐