2124: 等差子序列
Time Limit: 3 Sec Memory Limit: 259 MB
Description
给一个1到N的排列{Ai},询问是否存在1<=p1=3),使得Ap1,Ap2,Ap3,…ApLen是一个等差序列。
Input
输入的第一行包含一个整数T,表示组数。下接T组数据,每组第一行一个整数N,每组第二行为一个1到N的排列,数字两两之间用空格隔开。
Output
对于每组数据,如果存在一个等差子序列,则输出一行“Y”,否则输出一行“N”。
Sample Input
2
3
1 3 2
3
3 2 1
Sample Output
N
Y
HINT
对于100%的数据,N<=10000,T<=7
思路:
考虑枚举中间数x,如果x满足条件,也就是比它大和小的两个数y和z,x-y=z-x,并且这两个数要在x的两侧(如果我们考虑逐个枚举x,条件就是y已经出现过了而z还没有出现)。
因此,我们可以得到一个01串,其中x如果出现过,那么为1否则为0。
发现题目给的是1到n的排列,就是说每个数出现有且仅有一次,一个数之前只出现了等差数列中比它自身小的数,那么之后就一定会出现剩下那个比它大的数。
因此如果x满足条件,那么必然存在一个公差y使得[x-y]!=[x+y](01值不同,表示不在x的同一侧)。所以枚举等差中项,每次O(n)扫一遍左右两部分,然后我们发现问题就是判断以x为中心的极长字符串是否是回文串,如果不是那么显然存在y使得[x-y]!=[x+y]。
于是考虑hash,然后因为需要修改那么就用两个树状数组分别维护它所在区间正序和倒序的hash值即可。
hash真的很迷呀(来自一个没怎么写过的辣基)觉得自己的hash太丑陋了,只有慢慢做题摸索了。。。
#include <cstdio>
#include <cstring>
#include <iostream>
#define LL long long
using namespace std;
int const mod = 1000000007;
int const N = 10010;
int n, a[N], pw[N];
struct bit{
int c[N];//树状数组维护区间hash
void clear(){ memset(c, 0, sizeof(c)); }
void add(int x){
for(int i=x; i<=n; i+=(i&-i)) c[i] = (c[i] + pw[i-x]) % mod;//乘上距离(公差)的hash
}
int query(int x){
int sum = 0;
for(int i=x; i; i-=(i&-i))
sum = ( (LL) c[i] * pw[x-i] + sum ) % mod;//乘上距离(公差)的hash
return sum;
}
int Query(int l, int r){
int p = query(l-1), q = query(r);
return ( q - (LL) p * pw[r-l+1] % mod + mod) % mod;//乘上区间大小的hash
}
}order, reorder;//正序,反序
int main(){
int T; scanf("%d", &T);
pw[0] = 1;
for(int i=1; i<=10000; i++)
pw[i] = (LL)pw[i-1] * 107 % mod;
while( T-- ){
order.clear(); reorder.clear();
scanf("%d", &n);
for(int i=1; i<=n; i++)
scanf("%d", &a[i]);
int i;
for( i=1; i<=n; i++){
int cc = min(a[i]-1,n-a[i]);//左右个数要相等,所以取其中小的一边
if(cc && order.Query(a[i]-cc, a[i]-1) != reorder.Query(n-a[i]-cc+1, n-a[i])) break;
//可以找到等差
order.add(a[i]); reorder.add(n-a[i]+1);
}
puts( (i > n) ? "N" : "Y" );
}
return 0;
}