1. 程式人生 > >【BZOJ2795】[Poi2012]A Horrible Poem hash

【BZOJ2795】[Poi2012]A Horrible Poem hash

文字 sin const pri aaa n-n 答案 tchar 分解質

【BZOJ2795】[Poi2012]A Horrible Poem

Description

給出一個由小寫英文字母組成的字符串S,再給出q個詢問,要求回答S某個子串的最短循環節。
如果字符串B是字符串A的循環節,那麽A可以由B重復若幹次得到。

Input

第一行一個正整數n (n<=500,000),表示S的長度。
第二行n個小寫英文字母,表示字符串S。
第三行一個正整數q (q<=2,000,000),表示詢問個數。
下面q行每行兩個正整數a,b (1<=a<=b<=n),表示詢問字符串S[a..b]的最短循環節長度。

Output

依次輸出q行正整數,第i行的正整數對應第i個詢問的答案。

Sample Input

8
aaabcabc
3
1 3
3 8
4 8

Sample Output

1
3
5

題解:回憶用KMP求一個單詞最短循環節的方法,如果next[n]>=n/2,則最短循環節為n-next[n]。

那麽我們就得出了O(1)判斷一個長度x是否是給定串的循環節的方法,直接用hash判斷s[l...r-x]和s[l+x...r]是否相等即可,那麽我們思考這個最短長度是什麽。

首先如果x是該串的循環節,則2x,3x也一定是該串的循環節,並且我們已知原串長度len一定是它本身的循環節,那麽x一定是len的約數。我們可以將len分解質因數,x的每個質因子的次數一定<=len中每個質因子的次數,從大到小枚舉這個次數即可。

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
typedef long long ll;
const ll m1=998244353;
const ll m2=100000007;
const int maxn=500010;
int n,q,num,ans;
char str[maxn];
int pri[maxn],lp[maxn];
ll b1[maxn],b2[maxn],h1[maxn],h2[maxn];
bool np[maxn];
inline int rd()
{
	int ret=0,f=1;	char gc=getchar();
	while(gc<‘0‘||gc>‘9‘)	{if(gc==‘-‘)	f=-f;	gc=getchar();}
	while(gc>=‘0‘&&gc<=‘9‘)	ret=ret*10+gc-‘0‘,gc=getchar();
	return ret*f;
}
bool check(int a,int b,int c,int d)
{
	return (h1[b]-h1[a-1]*b1[b-a+1]%m1+m1)%m1==(h1[d]-h1[c-1]*b1[d-c+1]%m1+m1)%m1&&(h2[b]-h2[a-1]*b2[b-a+1]%m2+m2)%m2==(h2[d]-h2[c-1]*b2[d-c+1]%m2+m2)%m2;
}
int main()
{
	scanf("%d%s%d",&n,str+1,&q);
	int i,j,a,b,c;
	for(i=2;i<=n;i++)
	{
		if(!lp[i])	pri[++num]=lp[i]=i;
		for(j=1;j<=num&&i*pri[j]<=n;j++)
		{
			lp[i*pri[j]]=pri[j];
			if(i%pri[j]==0)	break;
		}
	}
	for(b1[0]=b2[0]=1,i=1;i<=n;i++)
	{
		h1[i]=(h1[i-1]*233+str[i])%m1,h2[i]=(h2[i-1]*233+str[i])%m2;
		b1[i]=b1[i-1]*233%m1,b2[i]=b2[i-1]*233%m2;
	}
	for(i=1;i<=q;i++)
	{
		a=rd(),b=rd(),c=ans=b-a+1;
		while(c!=1)
		{
			if(check(a,b-ans/lp[c],a+ans/lp[c],b))	ans/=lp[c];
			c/=lp[c];
		}
		printf("%d\n",ans);
	}
	return 0;
}//8 aaabcabc 3 1 3 3 8 4 8

【BZOJ2795】[Poi2012]A Horrible Poem hash