每日一题,坚持使我强大
今日份快乐: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;
}
欢迎大佬留言指错,哪里讲的不清楚也欢迎留言提出