Everything that kills me makes me feel alive.

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值时,其为回文串。

实际操作

  1. 更新 \(01\) 串。
    我们每次枚举到 \(a[i]\) 的时候将 \(a[i-1]\) 赋值为 \(1\)

  2. 线段树维护。
    我们每一个节点维护它所子孙中的所有叶子节点(叶子结点记录的就是数列上的数)的正反 \(hash\) 值,和它子孙中的叶子节点个数,记录为长度。每次上传将其与另一个它父亲的儿子进行合并操作。

  3. 具体合并操作。
    蒟蒻和机房另一个蒟蒻想到,既然是 \(01\) 串,为什么不直接采用二进制,合并的时候左移和右移即可,后来调了很久没有做出来,数据一大全都挂了,是因为直接左移和右移过大,所以要进行预处理操作

  4. 正hash值和反hash值。

  • 算正hash值时,左儿子正hash值左移右儿子的长度后,与右儿子的正hash值相加。

  • 算反hash值时,右儿子反hash值左移左儿子的长度后,与左儿子的反hash值相加。

  • 直接push_up时左/右移长度为另一儿子记录的长度,但是query的时候可能并没有访问某一节点子孙的所有叶子结点,所以我们要进行取 \(\min\) 操作后左/右移。

  1. 判断是否存在。
    左边取正hash值和右边取反hash值相等的时候为回文串,当前中心的等差数列不存在,一旦出现不相等,则存在。

  2. 一些小坑点(可能只有本蒟蒻会错)。

  • 每次一定要重新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
*/
posted @ 2022-08-03 09:28  wind_seeker  阅读(89)  评论(0)    收藏  举报
Live2D