线性数据结构---单调栈&单调队列笔记整理

线性数据结构---单调栈&单调队列

单调栈

概念

      单调栈,顾名思义,就是栈内数据呈严格单调递增或严格单调递减的栈(栈底到栈顶),同时,由定义可得,当有一个元素要入栈时,首先要将栈内比它大(或小)的所有元素出栈,再将该元素入栈,如该实例:

(使用5,3,4,2,6构造一个单调递增栈)

1.(此时栈空)将5入栈             

2.(此时栈顶为5,只有一个元素)将5出栈,将3入栈

3.(此时栈顶为3,只有一个元素)将4入栈

4.(此时栈顶为4,有两个元素)将4出栈

5.(此时栈顶为3,只有一个元素)将3出栈,将2入栈

6.(此时栈顶为2,只有一个元素)将6入栈

        我们用数组记录元素入栈之前(已弹出不符合要求的栈顶后)的栈顶元素,就可以发现,得到的数组是-1 -1 3 -1 2,观察发现,得到的数组就是每个元素左侧最近的比它小的数,同理,通过维护不同的单调栈,可以得到该元素左/右边第一个比它大/小的数,故此,可以通过维护不同的单调栈来解决此类问题

代码实现

模板(找小的数字)

题面概括

        输入n个数,输出每个数左边第一个比它小的数

思路

        模板题,直接递增单调栈模拟上述过程即可

代码

#include<iostream>
#include<stack> 
using namespace std;
stack<int> st;
int n,a;
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a;
		while(!st.empty()&&st.top()>=a){
			st.pop();
		}
		if(!st.empty()){
		    cout<<st.top()<<" ";
		}else{
            cout<<-1<<" ";
        }
		st.push(a);
	}
	return 0;
}

T2.发射站

题面概括

        有n个发射站,他们分别有高度hi和能量vi,每个发射站发射的能量都能被左右两边第一个比它高的发射站接收,求接受能量最多的发射站接受了多少能量

思路

        利用递减单调栈求出每个点左边第一个比它高度低的发射站(即可接收自己能量的发射站),同时,考虑优化方案,每个点对于左边第一个比它高度低的发射站,也是第一个它右边比它高度高的发射站,用数组记录下来每个发射站能接收的能量,再比较即可

代码

#include<iostream>
#include<stack> 
using namespace std;
stack<int> st;
int n,a[1000005],b[1000005];
int ans[1000005];
int maxx=-1;
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i]>>b[i];
		while(!st.empty()&&st.top()<a[i]){
			ans[i]+=b[st.top()];
			st.pop();
		}
		if(!st.empty()){
		    ans[st.top()]+=b[i];
		}
		st.push(i);
	}
	for(int i=1;i<=n;i++) maxx=max(maxx,ans[i]);
	cout<<maxx;
	return 0;
}

T3.区间最小值问题

题面概括

        给出n和一个长度为n的数列,要求找出一个子区间,使这个子区间的数字之和乘上子区间中的最小值最大。

思路

        如果枚举每一个区间,时间复杂度将是O(n^2),不符合数据要求,会时间超限,所以考虑另一种方案:枚举区间最小值,再对于每个区间最小值x用单调递增栈求出它左侧和右侧第一个比它小的数,下标分别为l和r,那么区间[l+1,r-1]就是以x作为区间最小值得到的区间,最后将以不同的x为区间最小值得到的区间进行比较即可

代码:

#include<iostream>
#include<stack> 
using namespace std;
stack<int> sta,stb;
int n,a[100005];
int leftt[100005],rightt[100005];
int sum[100005];
int maxx,ans,maxl,maxr;
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1;i<=n;i++){
		while(!sta.empty()&&a[sta.top()]>=a[i]){
			sta.pop();
		}
		if(sta.empty()) leftt[i]=-1;
		else leftt[i]=sta.top();
		sta.push(i);
	}
	for(int i=n;i>=1;i--){
		while(!stb.empty()&&a[stb.top()]>=a[i]){
			stb.pop();
		}
		if(stb.empty()) rightt[i]=-1;
		else rightt[i]=stb.top();
		stb.push(i);
	}
	for(int i=1;i<=n;i++){
		sum[i]=sum[i-1]+a[i];
	}
	for(int i=1;i<=n;i++){
		ans=(sum[rightt[i]-1]-sum[leftt[i]])*a[i];
		if(ans>maxx){
			maxx=ans;
			maxl=leftt[i]+1;
			maxr=rightt[i]-1;
		}
	}
	cout<<maxx<<endl<<maxl<<" "<<maxr;
	return 0;
}

T4.Patrik 音乐会的等待

题面:

        共有n个人,他们都有自己的身高hi,如果两个人之间没有人身高比两个人之中任何人高,那么就称这两个人可以互相看见,求共有多少对人可以互相看见。

思路

        为了防止重复统计,我们考虑只向一方向统计从1~n个人之中每个人向左(或向右)可以看到的人数,若a能看见b,则a.b两人一定可以互相看见,那么使用单调栈维护每个人单方向能看到的人数,再将其累加即可,但是当两个人身高相同时,则不会阻挡视线,但是必须要将其先出栈才能继续考虑后面的人,所以在将其出栈后还要将其在入栈,入栈时如果有多个身高相同的人,代码可能会超时,所以考虑栈内存储一个pair<身高,个数>,这样则需要将累加器加上其个数,而且如果出栈操作完成后栈非空,则当前的新栈顶也可以被看到,所以也要累加。

代码:

#include<iostream>
#include<stack> 
using namespace std;

long long n,a[500005];
long long ans;
struct node{
    int v;
    int c;
};
stack<node> st;
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1;i<=n;i++){
	    node p={a[i],1};
	    while(!st.empty()&&st.top().v<=p.v){
	        if(st.top().v==p.v){
	            p.c+=st.top().c;
	        }
	        ans+=st.top().c;
	        st.pop();
	    }
	    st.push(p);
	}
	cout<<ans;
	return 0;
}

T5.直方图中最大的矩形

题面概括

        给定n个连续的长方形的高hi,求这些长方能组成的最大矩形面积是多少(每个长方形的宽为1)

思路

        该题与T3类似,仍然考虑枚举区间最小值来计算区间范围,但要注意长方形的宽应该是r-l+1,而且不能将找不到更大值的点的左端点或右端点直接设成-1,左端点应为1,而右端点应为n,否则会错误的计算长方形的宽。

代码

#include<iostream>
#include<cstring>
#include<stack> 
using namespace std;
stack<int> sta,stb,kst;
int n,a[100005];
int leftt[100005],rightt[100005];
int sum[100005];
long long maxx,ans,maxl,maxr;
int main(){
	while(cin>>n&&n){
		sta=stb=kst;
		maxx=0;
	    memset(leftt,0,sizeof leftt);
	    memset(rightt,0,sizeof rightt);
    	for(int i=1;i<=n;i++){
    		cin>>a[i];
    	}
    	for(int i=1;i<=n;i++){
    		while(!sta.empty()&&a[sta.top()]>=a[i]){
    			sta.pop();
    		}
    		if(sta.empty()) leftt[i]=0;
    		else leftt[i]=sta.top();
    		sta.push(i);
    	}
    	for(int i=n;i>=1;i--){
    		while(!stb.empty()&&a[stb.top()]>=a[i]){
    			stb.pop();
    		}
    		if(stb.empty()) rightt[i]=n+1;
    		else rightt[i]=stb.top();
    		stb.push(i);
    	}
    	for(int i=1;i<=n;i++){
    	    ans=(1+(rightt[i]-1)-(leftt[i]+1))*a[i];
    		if(ans>maxx){
    			maxx=ans;
    		}
    	}
    	cout<<maxx<<"\n";
	}
	return 0;
}

单调队列

概念

        单调队列,顾名思义,就是站内元素满足严格单调递增或严格单调递减的队列(队首到队尾),由定义可得,当一个元素入队时,就要将队首或队尾的若干个元素出队以维护队列的单调性,维护时,一般队尾入队,如果加入该元素后队列不符合单调性,则先队尾出队,直到加入该元素后数列仍符合单调性,一般来说,使用单调队列来计算区间最小值,若超过区间规定长度,则通过队头出队来维护区间长度,如实例:

(使用3,1,2,4来维护长度为2的单调递增数列)

1.(此时队列空)将3入队。

2.(此时队尾为3,数列长度为1)将3出队,将1入队

3.(此时队尾为1,数列长度为1)将2入队

4.(此时队尾为2,数列长度为2)将1(队首)出队,将2入队

此时,若记录每个元素入队前的栈顶元素(前提条件:队列长度为2),就可以得到数列1,2,观察发现,该数列就是所有数中向左2个数字的区间内的最小值,(第一和第二个数区间长度不足2),据此就可以计算区间最大/小值。

代码实现

模板(滑动窗口)

题面概括:

        给出一个数列,求该数列中所有长度为k的区间中的最小值和最大值,分别输出。

思路:

        模板题,使用单调队列按上述过程模拟即可。

代码

#include<bits/stdc++.h>
using namespace std;
int a[1000005];
deque<int> q;
int main(){
    int n,k;
    cin>>n>>k;
    for(int i=1;i<=n;i++){
    	cin>>a[i];
	}
	for(int i=1;i<=n;i++){
		while(!q.empty()&&a[q.back()]>=a[i]){
			q.pop_back();
		}
		q.push_back(i);
		if(q.front()<i-k+1){
			q.pop_front();
		}
		if(i>=k){
			cout<<a[q.front()]<<" "; 
		}
	}
	cout<<"\n";
	q.clear();
	for(int i=1;i<=n;i++){
		while(!q.empty()&&a[q.back()]<=a[i]){
			q.pop_back();
		}
		q.push_back(i);
		if(q.front()<i-k+1){
			q.pop_front();
		}
		if(i>=k){
			cout<<a[q.front()]<<" "; 
		}
	}
    return 0;
}

T2.切蛋糕

题面概括:

        给出一个数列,求该数列长度为m的连续子区间中数字和的最大值。

思路:

        对于连续的区间和,我们可以考虑使用前缀和数组来计算,那么区间[l,r]的值就应该是sum[r]-sum[l-1],若要是该式最大,就要使sum[l-1]尽可能的小,我们考虑枚举sum[r]的值,来求出对于每个sum[r]值最小的sum[l-1],然后统计比较即可。

代码

#include<bits/stdc++.h>
using namespace std;
int a[1000005],sum[1000005],maxx;
long long leftt[1000005],rightt[1000005];
deque<long long> q;
int main(){
    int n,k;
    cin>>n>>k;
    for(int i=1;i<=n;i++){
    	cin>>a[i];
	}
	for(int i=1;i<=n;i++){
	    sum[i]=a[i]+sum[i-1];
	}
	for(int i=1;i<=n;i++){
		while(!q.empty()&&sum[q.back()]>=sum[i]){
			q.pop_back();
		}
		q.push_back(i);
		if(q.front()<i-k+1){
			q.pop_front();
		}
		if(!q.empty()){
		    maxx=max(maxx,sum[i]-sum[q.front()]);
		}
	}
	cout<<maxx;
    return 0;
}

T3.理想的正方形

题面概括:

        给出一个矩阵,求该矩阵中最大值与最小值的差最小的k*k的子矩阵中最大值与最小值的差。

思路:

        大概的思路是:
先用单调队列求出r_min[i][j]表示a[i][j]到a[i][j+y-1]中的最小值
再用单调队列求出mn[i][j]表示r_min[i][j]到r_min[i+x-1][j] 中的最小值 

在此时,mn[i][j]表示的就是以a[i][j]为左上角的k*k矩阵的最小值

求最大值同理。最后统计得出最小值即可。

代码:

#include<bits/stdc++.h>
using namespace std;
int a[1005][1005],maxx;
int mn_min[1005][1005],mn_max[1005][1005]; 
int r_min[1005][1005],r_max[1005][1005];
deque<int> q;
int minn=0x3f3f3f3f;
int main(){
    int n,m,k;
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++){
    	for(int j=1;j<=m;j++){
    	    cin>>a[i][j];
    	}
	}
	for(int i=1;i<=n;i++){
		q.clear();
	    for(int j=1;j<=m;j++){
    		while(!q.empty()&&a[i][q.back()]>=a[i][j]){
    			q.pop_back();
    		}
    		q.push_back(j);
    		if(q.front()<j-k+1){
    			q.pop_front();
    		}
    		if(j>=k){
                r_min[i][j]=a[i][q.front()];
    		}
	    }
	}
	for(int j=k;j<=m;j++){
		q.clear();
	    for(int i=1;i<=n;i++){
    		while(!q.empty()&&r_min[q.back()][j]>=r_min[i][j]){
    			q.pop_back();
    		}
    		q.push_back(i);
    		if(q.front()<i-k+1){
    			q.pop_front();
    		}
    		if(j>=k){
                mn_min[i][j]=r_min[q.front()][j];
    		}
	    }
	}
	for(int i=1;i<=n;i++){
		q.clear();
	    for(int j=1;j<=m;j++){
    		while(!q.empty()&&a[i][q.back()]<=a[i][j]){
    			q.pop_back();
    		}
    		q.push_back(j);
    		if(q.front()<j-k+1){
    			q.pop_front();
    		}
    		if(j>=k){
                r_max[i][j]=a[i][q.front()];
    		}
	    }
	}
	for(int j=k;j<=m;j++){
		q.clear();
	    for(int i=1;i<=n;i++){
    		while(!q.empty()&&r_max[q.back()][j]>=r_max[i][j]){
    			q.pop_back();
    		}
    		q.push_back(i);
    		if(q.front()<j-k+1){
    			q.pop_front();
    		}
    		if(j>=k){
                mn_max[i][j]=r_max[q.front()][j];
                minn=min(minn,mn_max[i][j]-mn_min[i][j]);
    		}
	    }
	}
	cout<<minn;
    return 0;
}

  • 13
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值