做题笔记:洛谷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;
}

posted @ 2023-02-02 10:43  Oier_szc  阅读(86)  评论(0)    收藏  举报