【每日一题】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;
}

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

单调栈是一种常用的数据结构,用于解决一类特定的问题,其中最常见的问题是找到数组中每个元素的下一个更大或更小的元素。在Codeforces编程竞赛中,单调栈经常被用于解决一些与数组相关的问题。 下面是单调栈的一般思路: 1. 创建一个空栈。 2. 从左到右遍历数组元素。 3. 对于每个元素,将其与栈顶元素进行比较。 - 如果当前元素小于等于栈顶元素,则将当前元素入栈。 - 如果当前元素大于栈顶元素,则将栈顶元素弹出,并将当前元素入栈。 4. 重复步骤3,直到遍历完所有元素。 这样,最后剩下的栈中元素就是没有下一个更大或更小元素的元素。在使用单调栈求解具体问题时,我们可以根据需要进行一些特定的操作。 例如,如果要找到一个数组中每个元素的下一个更大的元素,可以使用单调递减栈。具体操作如下: 1. 创建一个空栈和一个空结果数组。 2. 从左到右遍历数组元素。 3. 对于每个元素,将其与栈顶元素进行比较。 - 如果当前元素小于等于栈顶元素,则将当前元素入栈。 - 如果当前元素大于栈顶元素,则将栈顶元素弹出,并将其在结果数组中的位置记录为当前元素的下一个更大元素的索引。 4. 将当前元素入栈。 5. 重复步骤3和4,直到遍历完所有元素。 6. 结果数组中没有下一个更大元素的位置,可以设置为-1。 以下是一个使用单调递减栈求解下一个更大元素问题的示例代码: ```cpp #include <iostream> #include <stack> #include <vector> std::vector<int> nextGreaterElement(std::vector<int>& nums) { int n = nums.size(); std::vector<int> result(n, -1); std::stack<int> stack; for (int i = 0; i < n; i++) { while (!stack.empty() && nums[i] > nums[stack.top()]) { result[stack.top()] = i; stack.pop(); } stack.push(i); } return result; } int main() { std::vector<int> nums = {1,3, 2, 4, 5, 1}; std::vector<int> result = nextGreaterElement(nums); for (int i = 0; i < result.size(); i++) { std::cout << "Next greater element for " << nums[i] << ": "; if (result[i] != -1) { std::cout << nums[result[i]]; } else { std::cout << "None"; } std::cout << std::endl; } return 0; } ``` 以上代码将输出: ``` Next greater element for 1: 3 Next greater element for 3: 4 Next greater element for 2: 4 Next greater element for 4: 5 Next greater element for 5: None Next greater element for 1: None ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值