【每日一题】codeforces 574B (单调栈)

每日一题,坚持使我强大


今日份快乐:codeforces 547B 传送门

明天份快乐:codeforces 448C 传送门


题目大意

有 n 个小熊站成一排,每个小熊都有一个高度 ai。对于一个区间 [ L,R ],定义该区间的 strength 值为:该区间的最小值。
问:对于相同长度的区间 strength 值中的最大值为多少,从 len = 1 开始输出。


分析

我们正常的思路一般都是,先确定区间,再去找 strength 值,再更新 strength 值中的最值。本题的数据规模为 2e5,肯定要 O(n) 或者更高效的方法解决。


我们先来看如何确定空间,首先,如果我们暴力枚举所有可能的空间,时间复杂度要O(n2 / 2),肯定是行不通的。
这里我们这样转化:
对于给定的 a[x],我们去寻找它的左边和右边第一个小于它的数 a[l] 和 a[r],那么 a[x],便是区间(l,r)中的最小值,也就是 strength 值。a[x] 出现的区间长度最小是 1(它自己),最大时 r - l - 1(区间 l 到 r),这里我们去更新 1 到 r - l - 1 的 strength 值中的最值就OK。

给一个实例:
序列 n = 9,分别为 4, 1 ,5,6, 3 ,10,7, 2 ,8
在上例中,a[5] = 3 是区间 [ 3,7 ]中的最小值,也就是该区间的 strength 值( 长度为 5 ),同时 a[5] 也是长度为 1,2,3,4 的区间中的 strength 值
这里有一个结论:
长度的区间 strength 值中的最大值一定 大于等于 长度的区间 strength 值中的最大值
由这个结论,我们在得到区间 [ 3,7 ]中的 strength值后,只需要更新长度为 5 的区间 strength 值即可,
在最后,我们从 n-1 到 1,依次取 ans[i] = max(ans[i], ans[i+1]) 便可更新出每个长度区间的 strength 值中的最大值

现在的问题是如何高效的去找到每个数的左右最值,这里就要用到单调栈来解决(不知道‘栈’为何物的小伙伴移步这里 传送门)。

单调栈,分为单调递增栈和单调递减栈,我们这里要用的是单调递增栈。
单调递增栈是指:数据出栈的序列为单调递增序列。


现在有一组数10,3,7,4,12。从左到右依次入栈,则如果栈为空或入栈元素值小于栈顶元素值,则入栈;否则,如果入栈则会破坏栈的单调性,则需要把比入栈元素小的元素全部出栈。单调递减的栈反之。

  • 10入栈时,栈为空,直接入栈,栈内元素为10。
  • 3入栈时,栈顶元素10比3大,则入栈,栈内元素为10,3。
  • 7入栈时,栈顶元素3比7小,则栈顶元素出栈,此时栈顶元素为10,比7大,则7入栈,栈内元素为10,7。
  • 4入栈时,栈顶元素7比4大,则入栈,栈内元素为10,7,4。
  • 12入栈时,栈顶元素4比12小,4出栈,此时栈顶元素为7,仍比12小,栈顶元素7继续出栈,此时栈顶元素为10,仍比12小,10出栈,此时栈为空,12入栈,栈内元素为12。

这就是维护一个单调递增栈的过程,代码实现如下

for(int i = 1; i <= n; i++){
	while(!st.empty() && a[st.top()] >= a[i]) st.pop();	// 判断要入栈的元素和栈顶元素的关系
	st.push(i);						// 当前元素入栈
 }

单调栈的详细讲解:传输门

通过单调递增栈我们就可以找到比 a[x]小的第一个元素的位置,并纪录,如图
在这里插入图片描述
从左到右,比 a[2] 小的第一个元素为 a[1],比 a[3] 小的第一个元素为 a[2],比 a[4] 小的第一个元素为 a[3],比 a[5] 小的第一个元素为 a[4],a[6] 小的第一个元素为 a[4]。在a[6] 入栈时,a[5]就会先被弹出。这样,我们通过单调栈解决在 a[x] 左边,比 a[x]小的第一个数的位置 v_L[x]。
当从右到左时,我们可以发现,用同样的操作,可以解决在 a[x] 右边,比 a[x]小的第一个数的位置 v_r[x]。细节都在代码中~~~

代码

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

const int maxn = 2e5+5;
int a[maxn];
int v_l[maxn];
int v_r[maxn];
int ans[maxn] = {0};
stack<int>st;

int main() {

	ios::sync_with_stdio(false);
	
	int n;
	cin >> n;
	for(int i = 1; i <= n; i++) cin >> a[i];
	
	for(int i = 1; i <= n; i++){		// 统计 a[x] 左端第一个比他小的位置
		while(!st.empty() && a[st.top()] >= a[i]) st.pop();	// 维护单调栈
		if(st.empty()) v_l[i] = 0;	// 如果这时栈为空,记 v_l[i] = 0 
		else v_l[i] = st.top();		// 如果这时栈不为空,栈顶元素就是a[x] 左端第一个比他小的位置
		st.push(i);			// 入栈的是元素的在序列中的位置
	}
	
	while(!st.empty()) st.pop();		// 清空栈,避免对下面照成影响
	
	for(int i = n; i >= 1; i--){		// 统计 a[x] 右端第一个比他小的位置
		while(!st.empty() && a[st.top()] >= a[i]) st.pop();	// 维护单调栈
		if(st.empty()) v_r[i] = n+1;	// 如果这时栈为空,记 v_l[i] = n+1
		else v_r[i] = st.top();		// 如果这时栈不为空,栈顶元素就是a[x] 右端第一个比他小的位置
		st.push(i);
	}
	
	for(int i = 1; i <= n; i++){		// 计算区间 strength 值
		int len = v_r[i] - v_l[i] - 1;
		ans[len] = max(ans[len], a[i]);
	}
	for(int i = n - 1; i >= 1; i--) ans[i] = max(ans[i], ans[i+1]);		//更新最大值
	
	for(int i = 1; i <= n; i++) cout << ans[i] << " "; 
	
    return 0;
}

欢迎大佬留言指错,哪里讲的不清楚也欢迎留言提出

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
大学生参加学科竞赛有着诸多好处,不仅有助于个人综合素质的提升,还能为未来职业发展奠定良好基础。以下是一些分析: 首先,学科竞赛是提高专业知识和技能水平的有效途径。通过参与竞赛,学生不仅能够深入学习相关专业知识,还能够接触到最新的科研成果和技术发展趋势。这有助于拓展学生的学科视野,使其对专业领域有更深刻的理解。在竞赛过程中,学生通常需要解决实际问题,这锻炼了他们独立思考和解决问题的能力。 其次,学科竞赛培养了学生的团队合作精神。许多竞赛项目需要团队协作来完成,这促使学生学会有效地与他人合作、协调分工。在团队合作中,学生们能够学到如何有效沟通、共同制定目标和分工合作,这对于日后进入职场具有重要意义。 此外,学科竞赛是提高学生综合能力的一种途径。竞赛项目通常会涉及到理论知识、实际操作和创新思维等多个方面,要求参赛者具备全面的素质。在竞赛过程中,学生不仅需要展现自己的专业知识,还需要具备创新意识和解决问题的能力。这种全面的综合能力培养对于未来从事各类职业都具有积极作用。 此外,学科竞赛可以为学生提供展示自我、树立信心的机会。通过比赛的舞台,学生有机会展现自己在专业领域的优势,得到他人的认可和赞誉。这对于培养学生的自信心和自我价值感非常重要,有助于他们更加积极主动地投入学习和未来的职业生涯。 最后,学科竞赛对于个人职业发展具有积极的助推作用。在竞赛中脱颖而出的学生通常能够引起企业、研究机构等用人单位的关注。获得竞赛奖项不仅可以作为个人履历的亮点,还可以为进入理想的工作岗位提供有力的支持。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值