单调队列及单调栈

文章介绍了单调栈和单调队列的基本概念,以及如何在理想正方形和发射站问题中利用这两种数据结构求解,通过实例展示了如何维护队列和栈的有序性以求得最优解。
摘要由CSDN通过智能技术生成

知识点

单调栈

就是有序的栈,分为递增栈和递减栈。
如何维护栈的有序性呢?
假设有一个序列 {5 3 8 9 1},要构建递增栈:
5入栈
单调栈:5
3<5,因此先将5出栈,3入栈
单调栈:3
8>3,8直接入栈
单调栈:3 8
9>8,9直接入栈
单调栈:3 8 9
1<9,因此先将8,9出栈,1入栈
单调栈:1
我们可以发现,构建递增栈:当要入栈元素可以顺利入栈时,此时的栈顶为要入栈元素左侧第一个小于它的数;构建递增栈,当要入栈元素可以顺利入栈时,此时的栈顶为要入栈元素左侧第一个小于它的数
实现代码:

#include<iostream>
#include<stack>
#include<cstdio> 
using namespace std;
stack<int> st;
int main(){
	int n,x;
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&x);
		while(!st.empty() && st.top()>=x){
			st.pop();
		}
		st.push(x);
	}
	return 0;
}

单调队列

与单调栈相似,为了维护队列的有序性,我们采用双端队列。
单调队列很多时候可以代替单调栈
因为双端队列可以从两头进行入队出队,所以它可以控制队内元素个数,当发现数量超出限制,则从队头出队一些元素。
实现代码:

#include<iostream>
#include<deque>
#include<cstdio> 
using namespace std;
int a[1000005],n,k;
deque<int> dq;
int main(){
	scanf("%d %d",&n,&k);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
	}
	for(int i=1;i<=n;i++){
		while(!dq.empty() && a[dq.back()]>=a[i]){
			dq.pop_back();
		}
		dq.push_back(i);
		if(dq.front()<i-k+1){
			dq.pop_front();
		}
	}
	return 0;
}

例题

题目:理想的正方形

时间限制:1秒 内存限制:128M

题目描述

有一个n×m的整数组成的矩阵,现请你从中找出一个k×k的正方形区域,使得该区域所有数中的最大值和最小值的差最小。

输入样例

5 4 2
1 2 5 6
0 17 16 0
16 17 2 1
2 10 2 1
1 2 2 2

输出样例

1

数据描述

2 ≤ n , m ≤ 1000 , k ≤ n , k ≤ m , k ≤ 100 2≤n,m≤1000,k≤n,k≤m,k≤100 2n,m1000,kn,km,k100

理想的正方形解答

思路

这是一个二维单调队列问题,先求出每一行到j的最小值和最大值,再求一边每一列的最大值和最小值,最后作差求最小值。(第二遍要求列,所以外层循环按列遍历内层按行遍历)

代码

#include<iostream>
#include<deque>
#include<cstdio> 
using namespace std;
int a[1005][1005],n,m,k,r_minn[1005][1005],r_maxx[1005][1005];
deque<int> Max,Min;
int main(){
	scanf("%d %d %d",&n,&m,&k);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			scanf("%d",&a[i][j]);
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			while(!Min.empty() && a[i][Min.back()]>=a[i][j]){
				Min.pop_back();
			}
			Min.push_back(j);
			if(Min.front()<j-k+1){
				Min.pop_front();
			}
			while(!Max.empty() && a[i][Max.back()]<=a[i][j]){
				Max.pop_back();
			}
			Max.push_back(j);
			if(Max.front()<j-k+1){
				Max.pop_front();
			}
			if(j>=k){
				r_minn[i][j]=a[i][Min.front()];
				r_maxx[i][j]=a[i][Max.front()];
			}
		}
		Max.clear();
    	Min.clear();
	}
	int ans=1e9;
	for(int j=k;j<=m;j++){
		for(int i=1;i<=n;i++){
			while(!Min.empty() && r_minn[Min.back()][j]>=r_minn[i][j]){
				Min.pop_back();
			}
			Min.push_back(i);
			if(Min.front()<i-k+1){
				Min.pop_front();
			}
			while(!Max.empty() && r_maxx[Max.back()][j]<=r_maxx[i][j]){
				Max.pop_back();
			}
			Max.push_back(i);
			if(Max.front()<i-k+1){
				Max.pop_front();
			}
			if(i>=k){
				ans=min(r_maxx[Max.front()][j]-r_minn[Min.front()][j],ans);
			}
		}
		Max.clear();
    	Min.clear();
	}
	printf("%d",ans);
	return 0;
}

题目:发射站

时间限制:1秒 内存限制:128M

题目描述

某地有N个能量发射站排成一行,每个发射站i都有不相同的高度 H i H_i Hi​​ ,并能向两边(两端的发射站只能向一边)同时发射能量值为 V i V_i Vi
​​ 的能量,发出的能量只被两边最近的且比它高的发射站(位置比他高的)接收。显然,每个发射站发来的能量有可能被0或1或2个其他发射站所接受。
请计算出接收最多能量的发射站接收的能量是多少。

输入样例

3
4 2
3 5
6 10

输出样例

7

数据描述

对于40%的数据, 1 ≤ N ≤ 5000 , 1 ≤ H i ​ ≤ 1 0 5 , 1 ≤ V i ​​ ≤ 1 0 4 1≤N≤5000,1≤H_i​ ≤10^5 ,1≤V_i​​ ≤10^4 1N5000,1Hi105,1Vi​​104
对于70%的数据, 1 ≤ N ≤ 1 0 5 , 1 ≤ H i ​​ ≤ 2 × 1 0 9 ​​ , 1 ≤ V ​ i ​​ ≤ 1 0 4 1≤N≤10^5,1≤H_i​​ ≤2×10^9​​ ,1≤V​_i​​ ≤10^4 1N105,1Hi​​2×109​​,1Vi​​104
对于100%的数据, 1 ≤ N ≤ 1 0 6 , 1 ≤ H i ≤ 2 × 1 0 9 ​​ , 1 ≤ V i ≤ 10 ​ 4 1≤N≤10^6,1≤H_i≤2×10^9​​ ,1≤V_i ≤10​^4 1N106,1Hi2×109​​,1Vi104

发射站解答

思路

这就是在求每个数左边第一个大于他的数和右边第一个大于它的数,然后向求出来的塔增加接收到的能量,最后求最大值。(每轮要入栈的元素就是所有从栈中弹出元素右侧第一个大于他们的数

代码

#include<iostream>
#include<stack>
#include<cstdio> 
using namespace std;
stack<int> st;
int v[1000005],h[1000005],ans[1000005],maxx=0;
int main(){
	int n,x;
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d %d",&h[i],&v[i]);
		while(!st.empty() && h[st.top()]<h[i]){
			ans[i]+=v[st.top()];
			st.pop();
		}
		if(!st.empty()){
			ans[st.top()]+=v[i];
		}
		st.push(i);
	}
	for(int i=1;i<=n;i++){
		maxx=max(ans[i],maxx);	
	}
	printf("%d",maxx);
	return 0;
}
  • 20
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值