做题笔记:洛谷P6510
update:将lower_bound和upper_bound意思搞反了,已修改qwq。
一道很好的单调栈+二分题。题目传送门
当时这道题是看了扶苏大大的题解,对我的单调栈有一定启发意义,但是扶苏大大的题解有些抽象,所以来总结一下思路。、
作者的语文并不好,加上是博客园的新人,敬请谅解。
分析扶苏思路
核心思路是枚举B找A。
既然要用B找A,肯定要找到A和B的关系或是A的性质。
我们知道,B是AB序列中的最大值,那么也就是说AB中不会出现比B要大的数。
简单转换,可以得出,若B的左边存在一个数大于B,那么它一定在A的左边,也就是说A在其右边。
那么就得到了A的第一个性质:在B左边比B大的第一个数的右边。
同时,A自己也有一个性质:A为AB中的最小值
所以,问题就转换成了求两个东西:
$\ \ \ $1.B左边比B大的第一个数的位置。为简化表达,下文设其在数组 \(a\) 中的下标为 \(l\) 。
$\ \ \ $2.满足A在 \(l\) 右边,且为AB中最小值的A。
解决两个问题
看了这两个问题,是不是很熟悉?
我们显然可以用单调栈求解。
不妨建立两个栈,一个设为栈1,且有 \(stack[top] \le stack[top-1]\) ,一个为栈2,有 \(stack[top] > stack[top-1]\) 。
对于第一个问题:
我们可以在找到B要插入栈1的位置后(也就是将该弹的都弹了后),别急着插入,因为根据单调栈的性质,此时栈1的栈顶就是我们要找的 \(l\) 。
然后就将其在数组 \(a\) 中的下标记录下来。
对于第二个问题:
知道了上一个问题的解,现在就正式开始找A了。
首先,再次引用单调栈的原理,显然,设在找到B插入栈2的位置后,栈2里有一元素,其在数组 \(a\) 中下标为 \(i\) ,又设现在枚举到的B在数组 \(a\) 中的下标为 \(k\) 那么一定满足:
对于任意\(i < j \le k\),一定有 \(a_i < a_j\)
换句话说,就是 \(a_i\) 小于 \(i\) 与 \(k\) 之间所有元素(不含 \(i\) )。
知道了上面的结论,我们就可以得出,栈2里的每一个元素都满足问题二的第二个条件:A为AB中最小值。
并且显然其他数并不满足。
那么就可以着手于第一个条件。
为了让AB尽可能长,显然要找 \(l\)(根据问题一的结论,\(l\) 就是此时栈1的栈顶)的右边第一个满足的A。
由于有且只有栈2里的元素满足第二个条件,现在又看到了熟悉的 "右边第一个数" ,明显可以直接在栈里用二分,也就是upper_bound。
那么用upper_bound找到的那个元素就一定满足A的所有条件,是一个合法的A,更新答案即可。
code
当时是跟着扶苏的码打的
代码实现非常简单,上面有不理解的地方可以结合代码。
//writer:Oier_szc
#include <bits/stdc++.h>
//#define int long long
using namespace std;
const int N=1e5+5;
int n,a[N],ans=0;
int stk1[N],tt1=0;//后缀最大值
int stk2[N],tt2=0;//后缀最小值
int main()
{
scanf("%d",&n);
int cnt=0;
for(int i=1;i<=n;++i)
{
scanf("%d",&a[i]);
while(tt1&&a[stk1[tt1]]<a[i]) --tt1;
while(tt2&&a[stk2[tt2]]>=a[i]) --tt2;
//注意别急着插入
int find_A=upper_bound(stk2+1,stk2+1+tt2,stk1[tt1])-stk2;
//此时stk1的栈顶就是一个满足的l,那么可以在stk2里用upper_bound直接去找A
if(find_A!=tt2+1) ans=max(ans,i-stk2[find_A]+1);
//一定要先特判找不到的情况
stk1[++tt1]=i;
stk2[++tt2]=i;
}
printf("%d\n",ans);
return 0;
}