单调栈
栈
一种数据结构,是一种只能在一端进行插入和删除操作的特殊线性表
它按照先进后出的原则存储数据
先进入的数据被压入栈底,最后的数据在栈顶
单调栈
栈内部的元素是具有单调性的一种数据结构,分为单调递增栈和单调递减栈
性质
- 满足从栈底到栈顶的元素具有单调性
- 满足栈的先进后出特性,越靠近栈顶的元素越早出栈
实现
对于一个单调递增栈,若当前进栈的元素为 x x x
- 如果当前栈为空或者 x ≥ x\ge x≥ 栈顶元素则直接将 x x x 进栈;
- 如果 x < x< x<栈顶元素则不断将栈顶元素出栈直到满足栈为空或者 x ≥ x\ge x≥栈顶元素
例如:有一个数列
5
,
4
,
6
,
2
,
4
,
3
,
6
,
5
{5,4,6,2,4,3,6,5}
5,4,6,2,4,3,6,5,以此构造一个单调递增栈
对于每个数实现过程
- 5入栈
- 5出栈,4入栈
- 6入栈
- 4,6出栈,2入栈
- 4入栈
- 4出栈,3入栈
- 6入栈
- 6出栈,5入栈
代码
void push(int now) {
stack<int> s;
while (!s.empty() && s.top() >= now)s.pop();
s.push(now);
}
int s[1010], top = 0;
while (top > 0 && s[top] >= now)top--;
s[++top] = now;
作用
有一个长度为 n 的数组 a ,对于 1 < = i < = n 1<= i <=n 1<=i<=n ,求 L ( i ) = m a x ( j : j < i , a j < a i ) L(i) = max (j : j < i , a_{j} < a_{i}) L(i)=max(j:j<i,aj<ai)
找到每一个元素左边 第一个比它小的元素的位置
- 拿另外一个栈记录元素的位置,与单调栈做同步的出栈入栈操作。
- 使用结构体作为单调栈中的元素,同时记录值和位置。
- 以元素的位置作为单调栈中的元素,比较时通过位置查询元素的大小。
[USACO06NOV]糟糕的一天 Bad Hair Day
题意
农夫约翰有N (
N
≤
80000
N \leq 80000
N≤80000)N头奶牛正在过乱头发节。每一头牛都站在同一排面朝东方,而且每一头牛的身高为
h
i
h_{i}
hi
第
N
N
N头牛在最前面,而第
1
1
1头牛在最后面
对于第
i
i
i头牛,
i
i
i到第一个超过他身高的牛都可以被他看道
令
C
[
i
]
C[i]
C[i]为能被
i
i
i看到的牛的总数,求
∑
i
=
1
n
C
[
i
]
\sum_{i=1}^{n}C[i]
∑i=1nC[i]
分析
我们可以反过来看,所有超过他高度的牛都可以看到他
我们维护一个单调递减的单调栈,插入一个数后,在该数前面的数的数量都可以被他看到
代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
#pragma warning (disable:4996)
typedef long long LL;
const int maxn = 80005;
int stack[maxn];
int main() {
int top = 0, n, now;
LL ans = 0;
scanf("%d", &n);
while (n--) {
scanf("%d", &now);
while (top > 0 && now >= stack[top])top--;
ans += LL(top);
stack[++top] = now;
}
printf("%lld\n", ans);
}
Largest Rectangle in a Histogr
题意
在一条水平线上有n个宽为1的矩形,求包含于这些矩形的最大子矩形面积(图中的阴影部分的面积即所求答案)
分析
用单调栈记录该矩阵之前大于他的矩阵长度总合,即为前缀长度
出栈时记录该矩阵后面比他高的矩阵长度总和
int width = 0;
while (top > 0 && stack[top].first >= now) {
width += stack[top].second;
ans = max(ans, stack[top].first * width);
top--;
}
stack[++top] = pll(now, width + 1);
代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
#pragma warning (disable:4996)
typedef long long LL;
typedef pair<LL, LL> pll;
const int maxn = 100005;
pll stack[maxn];
LL a[maxn];
int main() {
int top, n;
while (~scanf("%d", &n) && n) {
for (int i = 1; i <= n; i++)scanf("%lld", &a[i]);
a[n + 1] = 0;
top = 0;
LL ans = 0, now, width;
for (int i = 1; i <= n + 1; i++) {
now = a[i]; width = 0;
while (top > 0 && stack[top].first >= now) {
width += stack[top].second;
ans = max(ans, stack[top].first * width);
top--;
}
stack[++top] = pll(now, width + 1);
}
printf("%lld\n", ans);
}
}
POJ2796 Feel Good
题意
给出一个数组
求一个子区间,使该区间和乘区间最小值最大
分析
枚举每个点最为最小值的区间和区间和的乘积(区间和用前缀和算)
区间左区间和右区间,用单调栈找与他最近的比他小的值就是边界
代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
#pragma warning (disable:4996)
typedef long long LL;
typedef pair<LL, LL> pii;
const LL maxn = 100005;
LL Stack[maxn], a[maxn];
LL sum[maxn];
pii l[maxn];
int main() {
LL n, top, now, Case = 0;
while (~scanf("%lld", &n)) {
//memset(sum, 0, sizeof(sum));
sum[0] = 0;
for (LL i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
sum[i] = sum[i - 1] + a[i];
}
top = 0; Stack[0] = 0;
for (LL i = 1; i <= n; i++) {
now = a[i];
while (top > 0 && a[Stack[top]] >= now)top--;
l[i].first = Stack[top] + 1;
Stack[++top] = i;
}
top = 0; Stack[0] = n + 1;
for (LL i = n; i >= 1; i--) {
now = a[i];
while (top > 0 && a[Stack[top]] >= now)top--;
l[i].second = Stack[top] - 1;
Stack[++top] = i;
}
LL ans = -1, g = 0;
for (LL i = 1; i <= n; i++) {
if ((sum[l[i].second] - sum[l[i].first - 1]) * a[i] > ans) {
ans = (sum[l[i].second] - sum[l[i].first - 1]) * a[i];
g = i;
}
}
if (Case++)printf("\n");
printf("%lld\n", ans);
printf("%lld %lld\n", l[g].first, l[g].second);
}
}