单调栈 整理 USACO06NOV、Largest Rectangle in a Histogr、POJ2796

单调栈

一种数据结构,是一种只能在一端进行插入和删除操作的特殊线性表
它按照先进后出的原则存储数据
先进入的数据被压入栈底,最后的数据在栈顶

单调栈

栈内部的元素是具有单调性的一种数据结构,分为单调递增栈和单调递减栈

性质

  1. 满足从栈底到栈顶的元素具有单调性
  2. 满足栈的先进后出特性,越靠近栈顶的元素越早出栈

实现

对于一个单调递增栈,若当前进栈的元素为 x x x

  • 如果当前栈为空或者 x ≥ x\ge x 栈顶元素则直接将 x x x 进栈;
  • 如果 x &lt; x&lt; 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,以此构造一个单调递增栈
对于每个数实现过程

  1. 5入栈
  2. 5出栈,4入栈
  3. 6入栈
  4. 4,6出栈,2入栈
  5. 4入栈
  6. 4出栈,3入栈
  7. 6入栈
  8. 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 &lt; = i &lt; = n 1&lt;= i &lt;=n 1<=i<=n ,求 L ( i ) = m a x ( j : j &lt; i , a j &lt; a i ) L(i) = max (j : j &lt; i , a_{j} &lt; a_{i}) L(i)=max(j:j<i,aj<ai)

找到每一个元素左边 第一个比它小的元素的位置

  • 拿另外一个栈记录元素的位置,与单调栈做同步的出栈入栈操作。
  • 使用结构体作为单调栈中的元素,同时记录值和位置。
  • 以元素的位置作为单调栈中的元素,比较时通过位置查询元素的大小。

[USACO06NOV]糟糕的一天 Bad Hair Day

题意

农夫约翰有N ( N ≤ 80000 N \leq 80000 N80000)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);
	}
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值