版权声明:本文为博主原创文章,可以转载但是必须声明版权。 https://blog.csdn.net/forever_shi/article/details/82353328
题意:给出一个由小写英文字母组成的字符串S,再给出q个询问,要求回答S某个子串的最短循环节。
题解:
我感觉网上有些题解在乱说,并不是n是循环节,n*k也一定是循环节的,要满足n*k|len才行的。
而且感觉好多做法代码对但是解释是不对的。
我们发现,对于一个区间,可能的循环节长度一定是原区间长度的约数,但是直接枚举约数复杂度是根号级别的,复杂度还是高了。我们考虑优化,我们发现其实不用每一个约数都枚举,我们发现,每一个区间n其实可以写成若干个质数的乘积的形式,如果x是当前的循环节,只有能整除x的k可能是更短的循环节,那么我们尝试用x除以一些质因数之后判断是否可以成为新的循环节,其实我们可以发现,先除以哪个质因数其实对最终的答案没有影响的。
所以我们线性筛求出质数,并且你把那些非质数的数也标记为筛掉它的质数,这样能减小点常数吧。
判断是否是循环节可以通过哈希判断,只要判断
和
的哈希值是否相同就可以了。
这样单次判断是 的,枚举质因数应该是严格小于log级别的,最后复杂度是 ,比网上很多直接暴力枚举因数的带根号做法要优秀一些。(但是我自己常数大)。
代码:
#include <bits/stdc++.h>
using namespace std;
int n,q,vis[500010],p[500010],cnt;
long long ha[500010],m[500010],b=233333;
char s[500010];
inline int check(int l1,int r1,int l2,int r2)
{
long long x,y;
x=ha[r1]-ha[l1-1]*m[r1-l1+1];
y=ha[r2]-ha[l2-1]*m[r2-l2+1];
if(x==y)
return 1;
else
return 0;
}
int main()
{
scanf("%d",&n);
scanf("%s",s+1);
vis[1]=1;
for(int i=2;i<=n;++i)
{
if(!vis[i])
{
vis[i]=i;
p[++cnt]=i;
}
for(int j=1;j<=cnt&&i*p[j]<=n;++j)
{
vis[i*p[j]]=p[j];
if(i%p[j]==0)
break;
}
}
m[0]=1;
for(int i=1;i<=n;++i)
m[i]=m[i-1]*b;
for(int i=1;i<=n;++i)
ha[i]=ha[i-1]*b+s[i];
scanf("%d",&q);
while(q--)
{
int l,r,ans;
scanf("%d%d",&l,&r);
ans=r-l+1;
for(int i=ans;i>1;)
{
int x=vis[i];
while(ans%x==0&&check(l,r-ans/x,l+ans/x,r))
ans/=x;
while(i%x==0)
i/=x;
}
printf("%d\n",ans);
}
return 0;
}