单调栈:单调递增或单调减的栈,主要通过push和pop两个操作巧妙的解决一些有关序列的问题。
首先肯定是模板题:链接: link.
题意:给出整数数列a[1]到a[n],求f[1]~f[n],f[i]表示第i位置后面第一个大于a[i]的数的下标。
思路:从左到右遍历,碰到一个数小于等于栈顶就入栈,否则依次出栈,同时出栈的下标的f[i]就是当前遍历到的这个下标。
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<stack>
#define ll long long
using namespace std;
const int mod = 998244353;
const int maxn =3e6+10;
ll a[maxn];
ll f[maxn];
stack<ll> s;
int main(){
int n;
cin >> n;
for(int i = 1; i <= n; i++){
scanf("%lld", a+i);
}
for(int i = 1; i <= n; i++){
if(s.empty() || a[i]<=a[s.top()]){ //当前下标的数小于等于栈顶下标的数
s.push(i);
}else{
while(s.size() && a[i]>a[s.top()]){ //当前下标的数大于栈顶下标的数
int top = s.top();
s.pop();
f[top] = i; //当前的下标一定是第一个大于top的下标
// cout << top << " " << i << endl;
}
s.push(i);
}
}
for(int i = 1; i <= n; i++){
if(i>1) cout << " ";
printf("%lld", a[i]);
}
return 0;
}
碰到的题目
牛客链接: link.
题意:有n个小于k的数,不改变数组的顺序,求字典序最小的1到k的子序列。
#include <bits/stdc++.h>
#define ll long long
#define T int T;scanf("%d", &T);while(T--)
using namespace std;
const int mod = 998244353;
const int maxn =1e6+10;
int a[maxn];
int last[maxn];
int vis[maxn];
vector<int> ans;
int main(){
int n,k;
cin >> n >> k;
for(int i = 1; i <= n; i++){
cin >> a[i];
last[a[i]] = i; //记录每个数最后一个位置的下标
}
for(int i = 1; i <= n; i++){
if(vis[a[i]]) continue; //记录当前数是不是已选,选了就continue
while(ans.size() && last[ans[ans.size()-1]]>i && ans[ans.size()-1]>a[i]){
//如果当前数小于栈顶并且后面还有栈顶这个数,那么先出栈,后面再入(为了字典序最小)
vis[ans[ans.size()-1]] = 0;
ans.pop_back();
}
vis[a[i]] = 1; //选当前数
ans.push_back(a[i]);
}
for(int i = 0; i < ans.size(); i++){
if(i) cout << " ";
cout << ans[i];
}
return 0;
}
再来几道经典例题
例1 链接: link.
题意 有n头牛,每头牛可以看到右侧比他矮的牛,求每头牛可看到牛的总数。
思路 利用单调栈,碰到一头牛判断与栈顶的牛的高度,低于栈顶的牛就把当前牛入栈(下标),否则,边出栈边计算下标差值(可看到牛的数量)。
注意 最后加一头1e9的牛便于栈清空和栈牛剩余牛的可见数量的计算。
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<stack>
#define ll long long
using namespace std;
const int mod = 998244353;
const int maxn =1e6+10;
ll a[maxn];
stack<ll> s;
int main(){
int n;
ll ans;
while(~scanf("%d", &n)){
ans = 0;
for(int i = 0; i < n; i++){
scanf("%lld", a+i);
}
a[n] = 1e9; //为了最后清空栈
for(ll i = 0; i <= n; i++){
if(s.size()==0 || a[i] < a[s.top()]){
s.push(i);
}else{
ll top;
while(s.size() && a[i] >= a[s.top()]){
top = s.top(); //出栈第top只牛
s.pop();
ans += (i-top-1); //计算top和i之间的牛的数量等于top这个牛可看到的数量
}
s.push(i); //把当前牛入栈(下标) 为了计算
}
}
printf("%lld\n", ans);
}
return 0;
}
例2 链接: link.
题意:已知n个矩形的高度,宽度都为1,求最大面积。
思路:利用单调栈,每次遇到一个矩形,判断与栈顶的关系,若当前高度>栈顶矩形,则入栈,否则,依次把栈里的矩形出栈,并计算以出栈的矩形为最高高度的大矩形的面积(当前的矩形不进入计算)。
注意:在最后加一块高度为-1的矩形便于最后栈的清空与栈内矩形计算。
代码
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<stack>
#define ll long long
using namespace std;
const int mod = 998244353;
const int maxn =1e6+10;
ll a[maxn];
stack<ll> s;
int main(){
int n;
ll ans;
while(~scanf("%d", &n)){
if(n==0) break;
ans = 0;
for(int i = 0; i < n; i++){
scanf("%lld", a+i);
}
a[n] = -1; //为了最后清空栈
ans = 0;
ll cnt = 0;
for(ll i = 0; i <= n; i++){
if(s.size()==0 || a[i]>=a[s.top()]){ //空栈或当前高度大于等于栈顶
s.push(i);
}else{
ll top;
while(s.size() && a[i] < a[s.top()]){
top = s.top(); //向左延伸的矩形下标
s.pop();
cnt = (i-top)*a[top]; //以出栈的矩形作为最高高度,i-top表示延伸到左侧的矩形数量
ans = max(ans,cnt);
}
s.push(top); //可延伸到的最左侧入栈
a[top] = a[i]; //入栈的高度为当前的高度
}
}
printf("%lld\n", ans);
}
return 0;
}