[BZOJ2124]等差子序列(hash)
题意
给出n个数的一个全排列(n<=1e5),询问是否存在三个数,使得这三个数成等差数列,多组询问
思路
枚举中间的数,显然在值域上两边的数成对称,那么如果存在等差数列,一定有一个比它小的数在左边,一个大的在右边,而且关于这个中间的数对称。换句话说,两个数的值如果关于该数对称,那么他们的位置就不能同时出现在该数的左边或者右边
假设有5个数的全排列2 3 1 5 4,我们把在当前数左边设为1,右边设为0(当前数随便在左边或者右边)
举个栗子:给定数列2 3 5 1 4
依次加入每个数
1.( 0 , 0, 0 , 0 , 0 )//初始状态
2.( 0 , 1, 0 , 0 , 0)//加入2
3.( 0 ,1 , 1 , 0 , 0 )//加入3,可以发现在原串中存在一个以3为中心的等差序列( 2 , 3 , 4)
结合上面对左右关系的讨论,可以发现此时在第三步加入3后,以3为中心左右不对称,这就意味着左边有个比3小的数,右边有个比它大的数,所以存在等差序列
于是原问题转化为求回文串,用hash同时维护对应顺着扫和逆着扫,查询就看对应的区间是否相等,不相等就说明有回文串
单点修改怎么办?线段树维护hash就行啦
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 10005
using namespace std;
typedef unsigned long long ULL;
const ULL base=1000000007;
int T,n,a[N];
ULL hash1[N<<2],hash2[N<<2],temp[N];
template <class T>
void read(T &x)
{
char c;int sign=1;
while((c=getchar())>'9'||c<'0') if(c=='-') sign=-1; x=c-48;
while((c=getchar())>='0'&&c<='9') x=(x<<1)+(x<<3)+c-48; x*=sign;
}
void init()
{
read(n);
for(int i=1;i<=n;++i) read(a[i]);
memset(hash1,0,sizeof(hash1));
memset(hash2,0,sizeof(hash2));
}
void modify(ULL *hash,int rt,int l,int r,int x,int val)
{
if(l==r)
{
hash[rt]=val;
return;
}
int mid=(l+r)>>1;
if(x<=mid) modify(hash,rt<<1,l,mid,x,val);
else modify(hash,rt<<1|1,mid+1,r,x,val);
hash[rt]=hash[rt<<1]*temp[r-mid]+hash[rt<<1|1];
}
ULL query(ULL hash[],int rt,int l,int r,int x,int y)
{
if(x<=l&&r<=y) return hash[rt];
int mid=(l+r)>>1;
if(y<=mid) return query(hash,rt<<1,l,mid,x,y);
if(x>mid) return query(hash,rt<<1|1,mid+1,r,x,y);
ULL h1=query(hash,rt<<1,l,mid,x,y),h2=query(hash,rt<<1|1,mid+1,r,x,y);
return h1*temp[min(r,y)-mid]+h2;//注意这里的min(r,y)-mid表示右边的size
}
int main()
{
read(T);
temp[0]=1;
for(int i=1;i<N;++i) temp[i]=temp[i-1]*base;
while(T--)
{
init();
for(int i=1;i<=n;++i)
{
int p=min(n-a[i],a[i]-1);
if(p&&query(hash1,1,1,n,a[i]-p,a[i]+p)!=query(hash2,1,1,n,(n-a[i]+1)-p,(n-a[i]+1)+p))
{
printf("Y\n");
goto u;
}
modify(hash1,1,1,n,a[i],1);
modify(hash2,1,1,n,n-a[i]+1,1);
}
printf("N\n");
u:;
}
return 0;
}