判断长度为3的等差数列(经典问题)

题目大意:

给定一个长度为\(n\)的排列,判断是否存在长度为\(3\)的等差数列。

数据范围:\(3\leq n\leq 3\times10^5\)

本题题解

考虑枚举“长度为\(3\)的等差数列”,中间的那个元素。朴素的想法是枚举原排列的每个位置\(i\),看这个位置左边每个值,右边每个值,有没有刚好和\(p_i\)之差相等的。如果再枚举这个差值,时间复杂度\(O(n^2)\)。不太好继续优化。

换个角度,考虑每个原排列里的每个\(v\),设它的出现位置为\(\text{pos}[v]\)。现在要判断:\(\text{pos}\)序列上\(v\)左边(即小于\(v\)的值里)每个元素和\(v\)右边每个对应的元素,是否一个大于\(\text{pos}[v]\),一个小于\(\text{pos}[v]\)(这样就意味着,在原序列里,两个值分别在\(v\)的两侧)。这里“对应”,指的是和\(v\)的距离一样,例如\(v-1\)\(v+1\)是一对,\(v-2\)\(v+2\)是一对,......。只要有任意一个\(v\),存在这样的“一对”,答案就是\(\text{YES}\)了。

来看看把枚举位置换成枚举有什么好处。(1) 原来是没有两边“位置”要“对应”这个要求的,现在有了,合法的位置对数一下子从\(O(n^2)\)降到了\(O(n)\)。(2) 原来两边的数要“与\(p_i\)的差相等”,现在两边的数只需要“一个大于\(\text{pos}[v]\),一个小于\(\text{pos}[v]\)”。从要确定值相等,变成只需要判断01两种情况(大于和小于),这个限制变得宽泛了许多,因而也更容易求解了。

把大于看做\(0\),小于看做\(1\)。我们的\(\text{pos}\)序列,就变成了一个\(01\)序列。如果按原序列里的出现顺序枚举值\(v\)(也就按\(\text{pos}[v]\)从小到大枚举),则相当于每次把序列里的一个\(0\)改成\(1\):也就是做单点修改。然后我们要判断左右两边(从后往前和从前往后)对应位置的\(01\)值是否相等。只要存在一对不相等,答案就是\(\text{YES}\)。这可以用\(\texttt{bitset}\)判断。时间复杂度\(O(\frac{n^2}{w})\)

\(\texttt{bitset}\)改成哈希,用线段树维护哈希值,则时间复杂度\(O(n\log n)\)

参考代码:

//problem:ZR1325
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

const int MAXN=3e5;
const ull BASE=13131;
int n,a[MAXN+5],pos[MAXN+5];
ull pw[MAXN+5];
struct SegmentTree{
	ull left_to_right[MAXN*4+5];
	ull right_to_left[MAXN*4+5];
	ull query_left_to_right(int p,int l,int r,int ql,int qr){
		if(ql<=l && qr>=r){
			return left_to_right[p];
		}
		int mid=(l+r)>>1;
		if(ql<=mid && qr>mid){
			return pw[min(r,qr)-mid]*query_left_to_right(p<<1,l,mid,ql,qr)
				+query_left_to_right(p<<1|1,mid+1,r,ql,qr);
		}
		if(ql<=mid)return query_left_to_right(p<<1,l,mid,ql,qr);
		if(qr>mid)return query_left_to_right(p<<1|1,mid+1,r,ql,qr);
		//cout<<"error "<<l<<" "<<r<<" "<<ql<<" "<<qr<<endl;
		assert(0);
	}
	ull query_right_to_left(int p,int l,int r,int ql,int qr){
		if(ql<=l && qr>=r){
			return right_to_left[p];
		}
		int mid=(l+r)>>1;
		if(ql<=mid && qr>mid){
			return pw[mid-max(l,ql)+1]*query_right_to_left(p<<1|1,mid+1,r,ql,qr)
				+query_right_to_left(p<<1,l,mid,ql,qr);
		}
		if(ql<=mid)return query_right_to_left(p<<1,l,mid,ql,qr);
		if(qr>mid)return query_right_to_left(p<<1|1,mid+1,r,ql,qr);
		assert(0);
	}
	void modify(int p,int l,int r,int pos,ull v){
		if(l==r){
			left_to_right[p]=right_to_left[p]=v;
			return;
		}
		int mid=(l+r)>>1;
		if(pos<=mid)modify(p<<1,l,mid,pos,v);
		else modify(p<<1|1,mid+1,r,pos,v);
		left_to_right[p]=pw[r-mid]*left_to_right[p<<1]+left_to_right[p<<1|1];
		right_to_left[p]=pw[mid-l+1]*right_to_left[p<<1|1]+right_to_left[p<<1];
	}
	SegmentTree(){}
}T;
int main() {
	cin>>n;
	pw[0]=1;
	for(int i=1;i<=n;++i)pw[i]=pw[i-1]*BASE;
	for(int i=1;i<=n;++i)cin>>a[i],pos[a[i]]=i;
	for(int i=1;i<=n;++i){
		if(a[i]!=1 && a[i]!=n){
			int len=min(a[i]-1,n-a[i]);
			if(T.query_left_to_right(1,1,n,a[i]+1,a[i]+len)!=T.query_right_to_left(1,1,n,a[i]-len,a[i]-1)){
				cout<<"YES"<<endl;
				return 0;
			}
		}
		T.modify(1,1,n,a[i],1);
	}
	cout<<"NO"<<endl;
	return 0;
}
posted @ 2020-06-14 23:43  duyiblue  阅读(497)  评论(3编辑  收藏  举报