当青训营遇上码上掘金
单调栈
在引入题目前,先介绍一下什么是单调栈。
适用范围
给定一个序列,求序列中的每一个数左边或者右边第一个比他大或者比他小的数在什么地方。
维护
单调递增栈:在保持栈内元素单调递增的前提下,如果栈顶元素大于要入栈的元素,则将其弹出。将新元素入栈
单调递减栈:在保持栈内元素单调递减的前提下,如果栈顶元素小于要入栈的元素,则将其弹出。将新元素入栈。
时间复杂度为 O(n)
详细解释
单调递增栈:对于将要入栈的元素来说,在对栈进行更新后(即弹出了所有比自己大的元素),此时栈顶元素就是数组中左侧第一个比自己小的元素;当栈内元素被弹出时,遇到的就是数组中右边第一个比自己小的元素。
单调递减栈:对于将要入栈的元素来说,在对栈进行更新后(即弹出了所有比自己小的元素),此时栈顶元素就是数组中左侧第一个比自己大的元素;当栈内元素被弹出时,遇到的就是数组中右边第一个比自己大的元素。
接青豆
有了单调栈的知识,那么引入需要解决的问题。 现有 n 个宽度为 1 的柱子,给出 n 个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。
解法
能存水的前提是,左右两边的高度都要大于 0。而水位的高度则取决于矮的一边,和木桶存水一样。 我们可以用两个 int 记录左右两边的已知最高高度。随着指针的移动,最高高度也会更新。 每一格的存水量,取决于当前块和下一块的高度差。 当后面的柱子高度比前面的低时,是无法接雨水的。 当找到一根比前面高的柱子,就可以计算接到的雨水。 这就可以用单调栈来解决。总体原则就是
- 当前高度小于等于栈顶高度,入栈,指针后移。
- 当前高度大于栈顶高度,出栈,计算出当前墙和栈顶的墙之间水的多少,然后计算当前的高度和新栈的高度的关系,重复第 2 步。直到当前墙的高度不大于栈顶高度或者栈空,然后把当前墙入栈,指针后移。
代码:
输入格式
第一行包含整数 n。
第二行包含 n 个非负整数,代表柱子的高度。
输出格式
输出一个整数,表示最大接水量。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define MAXN 200005
#define MAXM 200005
#define N 200005
#define M 200005
typedef pair<int, int> pii;
#define INF 0x3f3f3f3f
#define rep(i, x, y) for (int i = x; i <= y; i++)
#define per(i, x, y) for(int i = x; i >= y; i--)
#define pb emplace_back
#define endl "\n"
ll read() {
ll x=0,f=1;char ch;
do{ch=getchar();if(ch=='-') f=-1;} while(ch<'0'||ch>'9');
do{x=x*10+ch-48;ch=getchar();} while(ch>='0'&&ch<='9');
return x*f;
}
int n, h[MAXN], q[MAXN];
void solve() {
n = read();
for (int i = 1; i <= n; i++) {
h[i] = read();
}
int tt = 0, ans = 0;
h[0] = -1;
for (int i = 1; i <= n; i++) {
while (tt && h[q[tt]] < h[i]) {
if (tt >= 2) {
int l = i - q[tt - 1] - 1;
int hh = min(h[i], h[q[tt - 1]]) - h[q[tt]];
ans += l * hh;
}
tt--;
}
q[++tt] = i;
}
printf("%d\n", ans);
}
int main() {
//ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
solve();
return 0;
}