洛谷P6510 奶牛排队

题目大意:

​ 有n个数,满足条件左端点A严格最小右端点B严格最大的最长子串长度是多少。

思路:

​ 对每一个i,他都可以是A或者B 。

​ 先把他当成是B,我们向前找A。因为要求[A,B]中所有数都比B小,所以找到第一个比B大的数的后一个就是A的最小下标取值

​ 再把每一个i当成A,我们向后找B,因为[A,B]中每一个数都比A大,所以后面第一个比A小的数字的前一个就是B的最大下标取值

​ 这样做在向前找和向后找的过程中是有可能找不到的,比如说a[i]是所有数字中的最大值,他向前找第一个比他大的那当然是找不到的,所以我们可以将数组的a[0]置INF(无穷大)。同理,数组中最小值在向后找更小的时候也是找不到的,所以可以把数组的a[n+1]置成0(奶牛身高不可能是0)

举个例子:4,6,5,7,3:

洛谷P6510 奶牛排队_单调栈队列

看了上面的表格就有人要问,“为什么是‘不小于’‘不大于’啊?”。其实很简单,因为题中要求左右端点是分别严格小于大于区间中所有数的。那么有了上面的表格就可以得出对于每一个i作为右边界B时对应的A的有可能的最小Amin,以及对于每一个i作为左边界A时右边界B的有可能的最大Bmax。如下图:

洛谷P6510 奶牛排队_单调栈_02

如此之后我们就可以枚举每一个B,去找Amin,若Amin对应的Bmax是大于等于此时的B的那就是满足条件的。但这样是不对的,正确的解法是去找[Amin[B],B-1]中第一个满足条件的。但是这样每一个B对应[Amin[B],B-1]不就成了n^2的算法了嘛?好不容易想到这里不会GG了吧。

​ 我们调整一下思路,因为要找长度的最大,所以我们可以从后向遍历B,因为对于每一个B,可能找到的最长长度就是B(如果从第一个开始就一直递增到B,就是这样子的情况)。那么如果我们的答案大于等于了当前的B,就可以直接输出答案然后结束。

​ 当然,不反向遍历B也是可以的。我们可以正向遍历A,只要当前答案超过了n+1-A就行。

AC代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll INF = 0x3f3f3f3f;
const ll N = 2*1e5+10;

ll n , ans , a[N] ;
ll Amin[N] , Bmax[N] ;
ll q[N] , num ;

int main(){
	ios::sync_with_stdio(false);
	//freopen("inn.in","r",stdin) ;
	cin>>n ;
	for(ll i = 1 ; i <= n ; i ++ )cin>>a[i] ;
	
	a[0] = INF ; a[n + 1] = 0 ;
	q[0] = 0 ;
	for(ll i = 1 ; i <= n ; i ++ ){//单调栈做左边第一个大于
		while(num && a[q[num]] < a[i])num-- ;
		Amin[i] = q[num] + 1;
		q[++num] = i ;
	}
	
	memset(q,0,sizeof q) ;
	q[0] = n + 1 ;
	num = 0 ;
		
	for(ll i = n ; i > 0 ; i -- ){//单调栈做右边第一个小于
		while(num && a[q[num]] > a[i])num-- ;
		Bmax[i] = q[num] - 1 ;
		q[++num] = i ;
	}
	
	for(ll i = n ; i > 0 ; i -- ){
		if(ans >= i )break ;
		for(ll j = Amin[i] ; j < i ; j ++){
			if(Bmax[j] >= i){
				ans = max(ans , i - j + 1) ;
				break ;//因为j是从小到大的,所以i-j+1是从大到小的
                //只要找到以一个符合的j就是找到了该B对应的最大长度,就直接break。
			}
		}
	}
	
	cout<<ans ;
	
}