P2757 [国家集训队]等差子序列 题解
这道题目蒟蒻做了整整 \(4\) 个小时才做出来,所以特意写一篇题解记录并警示后人。
核心做法
这个做法不是蒟蒻想到的,蒟蒻还是太菜。
思路主要是枚举一个中间数 \(a[i]\) 然后判断 \(a[i]+k\) 和 \(a[i]-k\)是否在 \(a[i]\) 的异侧。
由于枚举 \(a[i]\) 很难优化,所以我们优化判断 \(a[i]+k\) 和 \(a[i]-k\) 。
具体做法是在原数列上标记在 \(a[i]\) 左边的为 \(1\),\(a[i]\) 右边的为 \(0\)。
然后我们想,如果这里新建一个 \(1\) 到 \(n\) 的 \(01\) 串记录每一个数字的 \(01\) 状态,那么当所有的 \(a[i]+k\) 和 \(a[i]-k\) 都在 \(a[i]\) 的同侧的话(不满足时),也就意味着 \(a[i]+k\) 和 \(a[i]-k\) 是同一个 \(01\) 状态,那么以 \(a[i]\)为中心,两端不超过边界的 \(01\) 串是不是就变成了一个回文串了呢。
考虑如何维护这一个回文串,从其他题解大家也可知道。
线段树维护hash值,左边正hash值等于右边反hash值时,其为回文串。
实际操作
-
更新 \(01\) 串。
我们每次枚举到 \(a[i]\) 的时候将 \(a[i-1]\) 赋值为 \(1\)。 -
线段树维护。
我们每一个节点维护它所子孙中的所有叶子节点(叶子结点记录的就是数列上的数)的正反 \(hash\) 值,和它子孙中的叶子节点个数,记录为长度。每次上传将其与另一个它父亲的儿子进行合并操作。 -
具体合并操作。
蒟蒻和机房另一个蒟蒻想到,既然是 \(01\) 串,为什么不直接采用二进制,合并的时候左移和右移即可,后来调了很久没有做出来,数据一大全都挂了,是因为直接左移和右移过大,所以要进行预处理操作。 -
正hash值和反hash值。
-
算正hash值时,左儿子正hash值左移右儿子的长度后,与右儿子的正hash值相加。
-
算反hash值时,右儿子反hash值左移左儿子的长度后,与左儿子的反hash值相加。
-
直接push_up时左/右移长度为另一儿子记录的长度,但是query的时候可能并没有访问某一节点子孙的所有叶子结点,所以我们要进行取 \(\min\) 操作后左/右移。
-
判断是否存在。
左边取正hash值和右边取反hash值相等的时候为回文串,当前中心的等差数列不存在,一旦出现不相等,则存在。 -
一些小坑点(可能只有本蒟蒻会错)。
- 每次一定要重新build,每一个节点的每一个值都需要清空后重新赋值。
- 记得先预处理左移操作和右移操作。
- 遇到 \(a[i]=1\) 或 \(a[i]=n\) 时直接跳过。
- 一定记得要开longlong。
code
#include<bits/stdc++.h>
#define int long long
#define ls rt<<1
#define rs rt<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
using namespace std;
const int N=5e5+10,mod=1e9+7;
int T,n,maxn,minn,a[N],base[N];
struct node{
int sum[2],len;//sum[0]记录正hash值,sum[1]记录反hash值
}t[N<<2];
bool flag=false;
inline int read(){
char ch=getchar();int res=0,f=1;
while(ch<'0'||'9'<ch){if(ch=='-')f=-1;ch=getchar();}
while('0'<=ch&&ch<='9') res=res*10+ch-'0',ch=getchar();
return res*f;
}
void init(){
base[0]=1;
for(int i=1;i<=N-10+1;i++)
base[i]=base[i-1]*2ll%mod;
}
void pushup(int rt){
t[rt].sum[0]=((t[ls].sum[0]*base[t[rs].len])%mod+t[rs].sum[0])%mod;
t[rt].sum[1]=((t[rs].sum[1]*base[t[ls].len])%mod+t[ls].sum[1])%mod;
}
void build(int rt,int l,int r){
if(l==r){t[rt].len=1,t[rt].sum[0]=t[rt].sum[1]=0;return;}
int mid=(l+r)>>1;
build(lson),build(rson);
t[rt].len=t[ls].len+t[rs].len;
t[rt].sum[0]=t[rt].sum[1]=0;
}
void update(int rt,int l,int r,int x){
if(l==r){t[rt].sum[0]=t[rt].sum[1]=1;return;}
int mid=(l+r)>>1;
if(x<=mid) update(lson,x);
else update(rson,x);
pushup(rt);
}
inline int query(int rt,int l,int r,int ql,int qr,int k){//k为0是求正hash值,1是求反hash值
if(ql<=l&&r<=qr) return t[rt].sum[k];
int mid=(l+r)>>1,Llen=max(mid-max(l,ql)+1,0ll),Rlen=max(min(r,qr)-mid,0ll),res=0;
if(!k){
if(ql<=mid) res+=(query(lson,ql,qr,k)*base[Rlen])%mod;//左正hash移右儿子长度
if(mid<qr) res+=query(rson,ql,qr,k)%mod;
}
else{
if(ql<=mid) res+=query(lson,ql,qr,k)%mod;
if(mid<qr) res+=(query(rson,ql,qr,k)*base[Llen])%mod;//右反hash移左儿子长度
}
return res%mod;
}
signed main(){
T=read();init();//预处理左移右移操作
while(T--){
n=read(),flag=false;build(1,1,n);//预处理
for(int i=1;i<=n;i++) a[i]=read();
for(int i=2;i<n;i++){
update(1,1,n,a[i-1]);//更新上一位为1
if(a[i]==1||a[i]==n) continue;//1,n直接跳过
int L=min(a[i]-1,n-a[i]);
if(query(1,1,n,a[i]-L,a[i]-1,0)!=query(1,1,n,a[i]+1,a[i]+L,1)){//不相等,不为回文串
puts("Y"),flag=true;
break;
}
}
if(!flag) puts("N");
}
return 0;
}
//一组no的数据
/*
1
10
1 9 5 3 7 2 10 6 4 8
*/
/*
N
*/