bzoj-2795 A Horrible Poem

题意:

给出一个长度为N的字符串,有Q次询问;

每次询问给出一个区间,求区间最短循环节长度;

N<=500000,Q<=2000000;


题解:
这题数据范围简直丧病= =渣电脑3s真的能跑出来吗。。。

不过这题在BZ上是可做的,50s我的程序10s出解了;

首先这问题画一画发现它绝对不是什么数据结构能维护的,因为这东西毫无可并性;

硬说的话多个相同的的连在一起的循环节可以合并变长,然并卵;

所以如果考虑暴力一些呢?

发现循环节长度一定是区间长度n的约数,可以枚举n的约数;

验证利用RKhash的特性,可以O(1)取出一段hash值;

比较[l,r]区间len是否为循环节,和比较[l+len-1,r]和[l,r-len+1]这两段的hash值是等价的;

时间复杂度为O(q√n),看起来。。并不能过!

继续优化,现在算法的瓶颈是枚举约数的部分;

那么我们利用质因数分解,将n分解为多个质因子的乘积;

如果进行一步线性筛的预处理,这一步可以做到log级!

具体就是将vis[i*pri[j]]=1这一步中再记录一个i*pri[j]的最小质因子为pri[j],然后分解时每次除这东西;

分解降到了log级,但是对一一枚举约数没什么作用;

这里又有一个性质,如果存在一个长度为len的循环节,那么对于满足k*len|n的k*len,都是循环节;

这个还是比较显然的,然后就可以倒着搞,每次用n除一个质因子,然后判断判断,复杂度O(nlogn)就可以过了;

代码中加了个读入优化,虽然并没有什么卵用;


代码:


#include<cctype>
#include<math.h>
#include<stdio.h>
#include<algorithm>
#define N 510000
typedef unsigned long long ll;
const int seed=131;
const int LEN=1<<16;
char str[N];
ll hash[N],Pow[N];
int pri[N],fp[N],st[N],top,cnt;
bool vis[N];
char getc()
{
	static char *S,*T,buf[LEN];
	if(S==T)
	{
		T=(S=buf)+fread(buf,1,LEN,stdin);
		if(S==T)
			return EOF;
	}
	return *S++;
}
int read()
{
	static char ch;
	static int D;
	while(!isdigit(ch=getc()));
	for(D=ch-'0';isdigit(ch=getc());)
		D=D*10+ch-'0';
	return D;
}
bool judge(int l,int r,int len)
{
	return hash[r-len]-hash[l-1]*Pow[r-l+1-len]==hash[r]-hash[l+len-1]*Pow[r-l+1-len];
}
void init(int n)
{
	Pow[0]=1;
	for(int i=1;i<=n;i++)
	{
		hash[i]=hash[i-1]*seed+str[i];
		Pow[i]=Pow[i-1]*seed;
	}
	for(int i=2;i<=n;i++)
	{
		if(!vis[i])
			pri[++cnt]=i,fp[i]=i;
		for(int j=1;j<=cnt&&i*pri[j]<=n;j++)
		{
			vis[i*pri[j]]=1;
			fp[i*pri[j]]=pri[j];
			if(i%pri[j]==0)
				break;
		}
	}
}
int main()
{
	int n,m,i,j,k,l,r,len,ans;
	scanf("%d%s%d",&n,str+1,&m);
	init(n);
	for(k=1;k<=m;k++)
	{
		l=read(),r=read();
		len=r-l+1;
		top=0;
		while(len!=1)
		{
			st[++top]=fp[len];
			len/=fp[len];
		}
		for(i=1,len=r-l+1;i<=top;i++)
		{
			if(judge(l,r,len/st[i]))
				len/=st[i];
		}
		printf("%d\n",len);
	}
	return 0;
}



你可能感兴趣的:(hash,循环节,bzoj)