BZOJ 2795:[POI2012]OKR-A Horrible Poem

题意:
询问次数有200万次,每次询问的长度有50万,需要求出询问的区间的最小循环节。
给出性质1:对于一个区间的循环节来说,设区间为[l,r],循环节长度为len,那么可得:[l,r-len]=[l+len,r] (我们用哈希可以做到O(1)实现)
现在我们已经有了快速判断是否是循环节的方法了,然后我们需要找出一个快速枚举循环节的方法。
不难想到,枚举一个区间的全部因数,这样的复杂度为O(sqrt(n)q),粗略计算一下发现貌似需要14秒的时间(恰巧可以出一档询问次数为10万次的部分分)
现在我们需要一个更快的做法枚举全部因数,比sqrt(n)快的且符合此题的貌似只有log(n)了。
给出性质2:如果[l,l+len-1]为[l,r]的循环节,即循环长度为len,且r-l+1为2len的倍数,则[l,l+len*2-1]也为[l,r]的循环节。
所以,我们可以转变一个思路:先想办法判断2len是否是一个循环节,如果2len不是一个循环节,那么len则肯定也不是了;如果2len是一个循环节,接下来再判断len是否是循环节。
考虑到循环节长度肯定是区间长度的因数,所以我们可以用区间长度不断地分解质因数。
记r-l+1=LEN
设LEN=prime[p1]prime[p2]…prime[pn],不保证prime均不同。
首先判断循环节是否可能是LEN/prime[p1],如果是的话,那么很高兴,现在的LEN就变成了LEN/prime[p1],接下来我们需要试试LEN/prime[p1]/prime[p2]了;如果不是,那么很不高兴,我们只能试试LEN/prime[p2]了。
对于一个区间长度为LEN的字符串,即使prime均为2,也只需要log(LEN)的复杂度,符合我们的期望。所以,感觉做好了?!?
其实还没有。
我们怎么知道LEN分解质因数会分解成什么样子呢?
如果不考虑pollard rho或者miller-rabin这种东西,我们正常的分解质因数是要sqrt(n)的,这样,不是又没变嘛…
考虑在get_prime中预处理出每个数的最小质因子,设LEN的最小质因子为num[LEN],这样,LEN变为LEN/num[LEN],LEN/num[LEN]变为(LEN/num[LEN])/num[LEN/num[LEN]],并且同时保留num[LEN],num[LEN/num[LEN]],就可以log(n)内分解完质因数了。
而预处理的复杂度则是线性筛素数复杂度O(n)。
#include <bits/stdc++.h>
#define int unsigned long long
using namespace std;
const int N=5e5+5,base=131;
int tot,prime[N],num[N];
bool mark[N];
int n,q,l,r,len,cnt;
int bin[N],hash[N],b[N];
char str[N];

inline void get_prime()
{
	mark[1]=true;
	for (register int i=2; i<=n; ++i)
	{
		if (!mark[i]) prime[++tot]=i,num[i]=i;
		for (register int j=1; j<=tot; ++j)
		{
			if (prime[j]*i>n) break;
			mark[prime[j]*i]=true;
			num[prime[j]*i]=prime[j];
			if (i%prime[j]==0) break;
		}
	}
}

inline int get(int x,int y){return hash[y]-hash[x-1]*bin[y-x+1];}

signed main(){
	scanf("%lld",&n);
	get_prime();
	scanf("%s",str+1);
	bin[0]=1;
	for (register int i=1; i<=n; ++i) bin[i]=bin[i-1]*base;
	for (register int i=1; i<=n; ++i) hash[i]=hash[i-1]*base+(int)str[i];
	scanf("%d",&q); 
	while (q--)
	{
		scanf("%lld%lld",&l,&r);
		len=r-l+1;
		cnt=0;
		while (len>1) b[++cnt]=num[len],len/=num[len];
		len=r-l+1;
		for (register int i=1; i<=cnt; ++i) 
		if (get(l,r-len/b[i])==get(l+len/b[i],r)) len/=b[i];
		printf("%lld\n",len);
	}
return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值