线性数据结构---单调栈&单调队列
单调栈
概念
单调栈,顾名思义,就是栈内数据呈严格单调递增或严格单调递减的栈(栈底到栈顶),同时,由定义可得,当有一个元素要入栈时,首先要将栈内比它大(或小)的所有元素出栈,再将该元素入栈,如该实例:
(使用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;
}