题目传送门:【BZOJ 2124】
题目大意:共输入 T 组数据。每组数据给一个 1 到 N 的排列,询问是否存在至少三个数按下标顺次排列组成等差数列。( N ≤ 10000,T ≤ 7 )
题目分析:
一道入门的字符串 Hash 题,不过这道题坑了我好久……
分析题意,由于数据为 1 到 N 的排列,因此每个数仅会出现一次。设三个呈等差数列出现的数为 a , b , c,考虑到这三个数有 2*b = a + c,于是我们遍历这个排列,当 a , b 都出现过,而 c 还没有出现时,我们可以判断 c 一定会在之后出现。根据这个性质,我们只需要考虑中间数 b,然后判断 b + k 和 b - k 是否出现在整个排列的同侧。如果不是,则可以直接得出此排列中有符合条件的等差数列,否则继续查找。
直接枚举 k 的复杂度为 O ( n² ),显然会超时。那么怎么样才能更快地判断是否存在呢?
我们将是否出现用 0 和 1 表示,如果两个数在同侧出现,意味着两数的 0 , 1 状态相同(要么都没出现,要么都出现在一侧)。这样,该问题实质上转化成了值域上的回文串问题:设中间数为 b,在值域上,如果 [ b - k , b - 1 ] 到 [ b + 1 , b + k ] 对应的出现状态不同,那么在之后一定会出现等差数列。当直接判断会 TLE 的时候,我们考虑用 Hash 判断 [ b - k , b - 1 ] 和 [ b + 1 , b + k ] 是否为回文串。判断是否为回文串,存正反的 Hash 值即可;支持单点修改,于是我们用两个树状数组存储 Hash 值,一个为正向 Hash,另一个为反向 Hash (即将高位调换一个方向)。
证明上述过程的正确性:把一个完整的串分成两个子串;将第二个串反转存储,如果两个串回文,则它们的 0 , 1 状态应当相同,因此它们的 Hash 值也相同,于是它们为回文串。在处理 Hash 值的时候,我们需要考虑到位置的因素,需要将不同位置上的 Hash 值乘上 Hash 基数的幂来达到消除位置影响的效果,进而判断两串是否回文。
下面附上代码:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef unsigned long long ULL;
const int MX = 10005;
const ULL BASE = 224473;
int n,seq[MX];
ULL pos[MX],neg[MX],q(ULL*,int);
bool vispos[MX],visneg[MX];
//pos:正向(postive),neg:反向(negtive),下同
inline int lowbit(int x){return x & -x;}
ULL mypow(ULL a,int b){
ULL r = 1,base = a;
while (b){
if (b & 1) r *= base;
base *= base;
b >>= 1;
}
return r;
}
ULL q(ULL *dict,int x){ //树状数组查询
ULL ans = 0;
for (int i = x;i;i -= lowbit(i))
ans += dict[i] * mypow(BASE,x - i);
return ans;
}
ULL query(ULL *dict,int lf,int rt){
return q(dict,rt) - q(dict,lf - 1) * mypow(BASE,rt - lf + 1);
}
void add(ULL *dict,int x){ //回文,向树状数组内添加该位置的值
for (int i = x;i <= n;i += lowbit(i))
dict[i] += mypow(BASE,i - x);
}
bool check(int x){
int f = min(x - 1,n - x);
int lf_pos = x - f,rt_pos = x - 1;
int lf_neg = n - x - f + 1,rt_neg = n - x; //f:区间大小;其余的变量名为范围
if (f == 0) return false;
if (f == 1) return vispos[x + 1] != vispos[x - 1];//区间仅为 1 则直接判断,节省时间
ULL Qpos = query(pos,lf_pos,rt_pos),Qneg = query(neg,lf_neg,rt_neg);
if (Qpos == Qneg) return false; //是回文串
return true; //不是回文串
}
void _init(){
memset(vispos,0,sizeof(vispos));
memset(visneg,0,sizeof(visneg));
memset(pos,0,sizeof(pos));
memset(neg,0,sizeof(neg));
}
int main(){
int T;
cin>>T;
while (T--){
bool flag = 0;
_init();
cin>>n;
for (int i = 1;i <= n;i++){
cin>>seq[i];
}
for (int i = 1;i <= n;i++){
vispos[seq[i]] = true;
visneg[n - seq[i] + 1] = true;
if (check(seq[i])){ //不是回文串,直接退出
flag = 1;
break;
}
add(pos,seq[i]);
add(neg,n - seq[i] + 1);
}
puts(flag ? "Y" : "N");
}
return 0;
}