【算法竞赛模板】单调队列与单调栈

【算法竞赛模板】单调队列与单调栈


在这里插入图片描述

一、概念解析

  • 单调栈:具有单调(递增或递减)性质和栈性质的数据结构
        时间复杂度为 O(n)
  • 单调队列:具有单调(递增或递减)性质和队列性质的数据结构
        时间复杂度可以是O(1)、O(logn)、O(n)

两者的区别:
  单调队列其实是单调栈的一个plus版本,或者说是具有[l, r]区间性质的单调栈(单调栈一般来说是[0, r]类型的)。
  换句话说,对于单调栈能做出来的题目,基本上单调队列也能操作出来,而且请注意一点,单调队列适用范围更加广阔。
  题外话,单调队列是动态规划算法一个必备的优化手段,在大公司面试题目中频频出现。

二、单调栈

  在栈顶push一个元素时,为了维护栈的单调性,需要在保证将该元素插入到栈顶后整个栈满足单调性的前提下弹出最少的元素。
  为什么是最少呢?如下例子,如果是最多的话,插入元素14时,我们还可以弹出元素81,这不符合我们想要的结果。

在这里插入图片描述

int stk[N];		// 数组stk存的是元素的下标
int tt = 0;		// 栈顶
for (int i = 1; i <= n; i++) {
	while (tt && check(stk[tt], i)) tt--;	// check意为判断栈顶何时出栈,while可以不止出栈一个元素
	stk[++tt] = i;
	......									// 其他按题目要求实现
}

👉 单调栈练习题:点我跳转👈
   👇练习题AC代码👇

#include <bits/stdc++.h>
using namespace std;

const int N = 1e6 + 10;
int h[N], v[N], stk[N], sum[N];
int n;

int main(){
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) scanf("%d%d", &h[i], &v[i]);
	
	int tt = 0;
	for (int i = 1; i <= n; i++) {
		while (tt && h[stk[tt]] <= h[i]) {	// 如果栈不空,并且新元素的高度比栈顶的大,将栈顶元素出栈
			sum[i] += v[stk[tt]];
			tt--;
		}
		sum[stk[tt]] += v[i];				// 因为第i个元素要入栈,根据题意加上相应能量
		stk[++tt] = i;						// 元素i入栈
	}
	
	int ans = 0;
	for (int i = 1; i <= n; i++) ans = max(ans, sum[i]);
	printf("%d", ans);
}

三、单调队列

  单调队列对于任何一个数而言,只会在队列中出现一次,一旦这个数对于最后答案没有贡献了就会及时地将它删除。单调队列限制只能从队尾插入,但能从两端删除(双端队列),队列中保持单调性。对于一个数而言,新插入的数是具有潜力的并且自身价值比它还高的,那么它就出队。

int q[N];	// 数组q存的是元素的下标
int hh = 1, tt = 0;	// 队头、队尾
for (int i = 1; i <= n; i++) {
	while (hh <= tt && check_out(q[hh])) hh++;		// check_out判断队头是否超出队列长度,while可以不止出队一个元素
	while (hh <= tt && check(q[tt], i)) tt--;		// check判断队尾何时出队
	q[++tt] = i;									// 队尾重新指向下标
	......											// 其他按题目要求实现
}

  check_outcheck是我们需要根据题意去实现的,如下练习题可以帮助更好掌握单调队列
👉二分+单调队列练习题:点我跳转 👈
   👇练习题AC代码👇

#include <bits/stdc++.h>
using namespace std;

const int N = 1e5 + 10;
int n, s, t;
int nums[N], q[N];
double sum[N];

// 对当前为i的这个位置,设前t个前缀和最小的一个位置为j
// 然后sum[i]-sum[j]便是前缀和最大的那一个
bool check(double mid)
{
	for (int i = 1; i <= n; i++)
		sum[i] = sum[i-1] + nums[i] - mid;

	int hh = 1, tt = 0;
	for (int i = s; i <= n; i++) {
		while (hh <= tt && i-q[hh] > t) hh++;
		// 单调队列维护最优值,根据题意,显然sum[a]<=sum[b]&&a>b说明第i个没什么用
		while (hh <= tt && sum[q[tt]] > sum[i-s]) tt--;
		q[++tt] = i-s;
		// 队首是最小的,前缀和最大便是sum[i]-sum[q[hh]]
		// 但是这个序列长度可能大于T,所以才有前面的i-q[hh] > t
		if (hh <= tt && sum[i]-sum[q[hh]] >= 0) return true;
	}
	return false;
}

int main(){
	cin >> n >> s >> t;
	for (int i = 1; i <= n; i++) scanf("%d", &nums[i]);
	double l = -1e4, r = 1e4;
	while (r - l > 1e-4) {
		double mid = (l + r) / 2;
		if (check(mid))  l = mid;
		else r = mid;
	}
	printf("%.3f\n", l);
}



路漫漫其修远兮,吾将上下而求索

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ac君

在你们的鼓励下我会多多分享代码

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值