[BZOJ 2124] 等差子序列 Hash+树状数组(附粗略证明)

题目传送门:【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;
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值