单调栈和单调队列

单调栈

从名字上就听的出来,单调栈中存放的数据应该是有序的,所以单调栈也分为单调递增栈和单调递减栈

单调递增栈:单调递增栈就是从栈底到栈顶数据是从大到小
单调递减栈:单调递减栈就是从栈底到栈顶数据是从小到大

我们可以去看道例题
链接:点击

柱形图中最大的矩形
Description

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。

求在该柱状图中,能够勾勒出来的矩形的最大面积。
在这里插入图片描述
在这里插入图片描述
通过题目我们会发现如果我们计算当前柱高的矩形最大面积时,只有当它的两端比它要高或者相等时才可以扩展,所以我们发现需要去找左边第一个比它低的和右边第一个比它低的那个柱子。
这题如果你直接去遍历,到每个柱子时都前后遍历一遍,时间复杂度太高。
因此需要用单调栈,并且单调栈就是用来解决这一系列问题的
如果当前柱子比栈顶的柱子要高,说明栈顶的柱子就是当前柱子左边第一个比它低的柱子,直接用个数组来记录一下
如果当前柱子比栈顶的柱子要低,说明当前柱子时就是栈顶的柱子右边第一个比它低的柱子,用个数组来记录一下,并且将栈顶元素出栈,因为每一个元素入栈的时候都会找到左边第一个低的,左右都找完了就要退出,如果当前栈顶还有元素存在,说明有比左边有当前柱子低的,直接标记一下
这题两端需要处理一下,因为有可能左边没有比当前柱子低的或者右边没有,因此在一开始给左右标记数组一个初始值

AC代码如下
int l[100001], r[100001], s[100001], cnt;
    int largestRectangleArea(vector<int>& heights) {
        for(int i = 0; i < heights.size(); i++) l[i] = -1, r[i] = heights.size();
        for(int i = 0; i < heights.size(); i++) {
            while(cnt && heights[s[cnt - 1]] > heights[i]) {
                r[s[cnt - 1]] = i;
                cnt--;
            }
            if(cnt) l[i] = s[cnt - 1];
            s[cnt++] = i;
        }
        int ans = 0;
        for(int i = 0; i < heights.size(); i++) {
            ans = max(ans, (r[i] - l[i] - 1) * heights[i]);
        }
        return ans;
    }

单调队列

单调队列主要是用来处理给你一个区间,问你每个连续的k个区间内的最大值或者最小值是什么,并且输出这类问题的

单调队列就是让你当前队列中要么递增要么递减

要求的是每连续的 个数中的最大(最小)值,很明显,当一个数进入所要 “寻找” 最大值的范围中时,若这个数比其前面(先进队)的数要大,显然,前面的数会比这个数先出队且不再可能是最大值。

也就是说——当满足以上条件时,可将前面的数 “弹出”,再将该数真正 push 进队尾。

这就相当于维护了一个递减的队列,符合单调队列的定义,减少了重复的比较次数,不仅如此,由于维护出的队伍是查询范围内的且是递减的,队头必定是该查询区域内的最大值,因此输出时只需输出队头即可。

显而易见的是,在这样的算法中,每个数只要进队与出队各一次,因此时间复杂度被降到了 。

例题 点击
AC代码
#include<cstdio>

using namespace std;
const int N = 1e7 + 100;
int a[N], n, k;
int head, tail, q[N];

void getMin() {
	head = 0, tail = -1;
	for(int i = 1; i < k; i++) {
		while(head <= tail && a[q[tail]] >= a[i]) tail--;
		q[++tail] = i;
	}
	for(int i = k; i <= n; i++) {
		while(head <= tail && a[q[tail]] >= a[i]) tail--;
		q[++tail] = i;
		while(q[head] <= i - k) head++;
		printf("%d ", a[q[head]]);
	}
	printf("\n");
}

void getMax() {
	head = 0, tail = -1;
	for(int i = 1; i < k; i++) {
		while(head <= tail && a[q[tail]] <= a[i]) tail--;
		q[++tail] = i;
	}
	for(int i = k; i <= n; i++) {
		while(head <= tail && a[q[tail]] <= a[i]) tail--;
		q[++tail] = i;
		while(q[head] <= i - k) head++;
		printf("%d ", a[q[head]]);
	}
	printf("\n");
}

int main() {
	scanf("%d%d", &n, &k);
	for(int i = 1; i <= n; i++) scanf("%d", a + i);
	getMin();
	getMax();
	return 0;
}

单调栈练习

1.Neat Tree
Description

It’s universally acknowledged that there’re innumerable trees in the campus of HUST.

There is a row of trees along the East-9 Road which consists of N trees. Now that we know the height of each tree, the gardeners of HUST want to know the super-neatness of this row of trees. The neatness of a sequence of trees is defined as the difference between the maximum height and minimum height in this sequence. The super-neatness of this sequence of trees is defined as the sum of neatness of all continous subsequences of the trees.

Multiple cases please process until the end of input. There are at most 100 test cases.

Input

For each case:
The first line contains an integer N( 1 ≤ N ≤ 1 0 6 1\leq N \leq 10^{6} 1N106)( 1 ≤ N ≤ 1 0 6 1≤N≤10^6 1N106) represents the number of trees.
The next line n positive integers followed, in which hi( 1 ≤ h i ≤ 1 0 6 1\leq h_{i} \leq 10^{6} 1hi106)( 1 ≤ h i ≤ 1 0 6 1≤h_i≤10^6 1hi106) represent the height of the tree i.

Output

For each test case, output a integer in a single line which represents the super-neatness of the row of trees.

示例输入
3
1 3 1
4
1 2 3 4
示例输出
6
10
题目大意和分析

这题是说给你 n 个点,每个点都有一个值,问你在所有的区间中最大值减去最小值的答案总和是多少
这题直接去枚举所有区间显然不太显示,那么我们可以换种想法,这题让找的不就是每个点作为区间内的最大值或者最小值的次数吗,也就是说这题的答案就是所有值作为最大值的和减去所有值作为最小值的和
那么我们就想到了单调栈,比如说求区间内每个点作为最大值的情况,由于是连续的区间,那么我们是不是只要去找左边第一个点比当前点大的点(记为 l l l)还有右边第一个点比当前点大的点(记为 r r r),然后这个点做为最大值得次数是不是就是等于 ( r − i ) ∗ ( i − l ) (r -i) * (i-l) (ri)(il)
最后求个和即可
求最小值得情况一样

AC代码
#include<cstdio>
#include<algorithm>

using namespace std;
typedef long long ll;
const int N = 1E6 + 10;
ll a[N];
int s[N], l[N], r[N];
int n;

ll getMax() {
	int cnt = 0;
	for(int i = 1; i <= n; i++) l[i] = 0, r[i] = n + 1;
	for(int i = 1; i <= n; i++) {
		while(cnt && a[s[cnt - 1]] < a[i]) {
			r[s[cnt - 1]] = i;
			cnt--;
		}
		if(cnt) l[i] = s[cnt - 1];
		s[cnt++] = i;
	} 
	ll ans = 0;
	for(int i = 1; i <= n; i++) {
		ans += a[i] * ((1ll * r[i] - i) * (1ll * i - l[i]));
	}
	return ans;
}

ll getMin() {
	int cnt = 0;
	for(int i = 1; i <= n; i++) l[i] = 0, r[i] = n + 1;
	for(int i = 1; i <= n; i++) {
		while(cnt && a[s[cnt - 1]] > a[i]) {
			r[s[cnt - 1]] = i;
			cnt--;
		}
		if(cnt) l[i] = s[cnt - 1];
		s[cnt++] = i;
	} 
	ll ans = 0;
	for(int i = 1; i <= n; i++) {
		ans += a[i] * ((1ll * r[i] - i) * (1ll * i - l[i]));
	}
	return ans;
}

int main() {
	while(~scanf("%d", &n)) {
		for(int i = 1; i <= n; i++) scanf("%lld", a + i);
		ll maxx = getMax();
		ll minn = getMin();
		printf("%lld\n", maxx - minn);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值