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
aaabcabc
3
1 3
3 8
4 8
Sample Output
1
3
5
3
5
题解Here!
首先这问题画一画发现它绝对不是什么数据结构能维护的,因为这东西毫无可并性。。。
于是我们拿出了处理字符串的暴力工具——$Hash$!
(当然不用后缀数组辣!)
首先最短循环节长度有几个性质:
- 循环节一定是长度的约数。
- 如果$n$是一个循环节,那么$k*n$也必定是一个循环节(关键所在)。
- 若$n$是$[l,r]$这一段的循环节的充要条件是$[l,r-n]$和$[l+n,r]$相同(利用这个性质我们在判断是否为循环节是可以做到$O(1)$)。
所以我们可以在求出这个区间的长度之后,判断它的每个约数是否是循环节(应用性质3),并且因为性质1,它的约数是循环节,原串一定也是。
所以只要不断除以质因数(相当于从大到小枚举约数),缩小$L$的长度,最后就是最小的长度。
而一个重要的点在于,用上面方法来枚举约数是为了避免$TLE$。
在求它的质因数的时候,可以通过线性筛的过程求得,将时间复杂度由$O(\sqrt n)$降为$O(\log_2n)$。
所以总复杂度就是$O(n\log_2n)$。
注意这里枚举到全长。
附代码:
#include<iostream>
#include<algorithm>
#include<cstdio>
#define MAXN 500010
#define MOD 29
using namespace std;
int n,m;
int val[MAXN],hash[MAXN];
char str[MAXN];
int k=0,prime[MAXN],min_prime[MAXN];
bool np[MAXN];
inline int read(){
int date=0,w=1;char c=0;
while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();}
return date*w;
}
void make(){
int m=MAXN-10;
for(int i=2;i<=m;i++){
if(!np[i]){
prime[++k]=i;
min_prime[i]=i;
}
for(int j=1;j<=k&&prime[j]*i<=m;j++){
np[prime[j]*i]=true;
min_prime[prime[j]*i]=prime[j];
if(i%prime[j]==0)break;
}
}
val[0]=1;
for(int i=1;i<=n;i++){
val[i]=val[i-1]*MOD;
hash[i]=hash[i-1]*MOD+(str[i]-'0'+1);
}
}
inline bool check(int l1,int r1,int l2,int r2){
int x=hash[r1]-hash[l1-1]*val[r1-l1+1],y=hash[r2]-hash[l2-1]*val[r2-l2+1];
return (x==y);
}
void work(){
int x,y;
while(m--){
x=read();y=read();
int ans=y-x+1;
for(int j=y-x+1;j>1;){
int k=min_prime[j];
while(ans%k==0&&check(x,y-ans/k,x+ans/k,y))ans/=k;
while(j%k==0)j/=k;
}
printf("%d\n",ans);
}
}
void init(){
n=read();
scanf("%s",str+1);
make();
m=read();
}
int main(){
init();
work();
return 0;
}