字符串哈希+质因数分解
题意
给出一个由小写英文字母组成的字符串S,再给出q个询问,要求回答S的某个子串的最短循环节。
如果字符串B是字符串A的循环节,那么A可以由B重复若干次得到。
∣S∣≤500000,q≤2000000
题解
初看此题,感觉与 UVA10298 Power Strings 十分相似(但那题数据水啊,字符串hash+暴力即可),此题询问过百万肯定TLE。
考虑优化,首先一个很显然的命题: 判断区间[l,r]构成循环节长度为len的循环只要判断hash[l,r−len]=hash[l+len,r]即可。这个证明很简单,不再赘述。
继续优化,可以发现循环节的个数是串长与串内每个字符个数的因数。也就是说要求出串长与串内每个字符个数的最大公约数g,再枚举g的因子,每次O(∣s∣),∣s∣为询问的串长。如果数据够水应该是能过的,但我们还有更优的做法。
假设我们已经证明循环节的长度可为len,就可以把原询问缩小,求当前长度为len的字符串的最短循环节。这实际上就是在做质因数分解,枚举质因数pi,若使len/pi成为可行的循环节长度,就更新ans=len/pi。考虑到线性筛素数时每一个数背其最小的质因数筛到,记录一些v[i]表示i最小的质因数方便分解质因数。
code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| #include <bits/stdc++.h> typedef unsigned long long ull; using namespace std; const int N = 500010; const ull d = 27ull; char s[N]; ull hsh[N],pw[N]={1ull}; int n,p[N],v[N],tot,q; void init() { for(int i=1;i<=n;i++) { pw[i]=pw[i-1]*d; } for(int i=1;i<=n;i++){ hsh[i]=hsh[i-1]*d+(ull)(s[i]-'a'+1); } } void initp(int n) { for(int i=2;i<=n;i++) { if (!v[i]) p[tot++]=i,v[i]=i; for(int j=0;j<tot;j++) { if (i*p[j]>n) break; v[i*p[j]]=p[j]; if (i%p[j]==0) { break; } } } } ull gethash(int l,int r){ return hsh[r]-hsh[l-1]*pw[r-l+1]; } int main(){ scanf("%d%s",&n,s+1); init();initp(n); scanf("%d",&q); while(q--) { int l,r; scanf("%d%d",&l,&r); int len=r-l+1,p=v[len],pp=len; while(p) { int k=pp/p; if(gethash(l,r-k)==gethash(l+k,r)) pp=k; len/=p; p=v[len]; } if(gethash(l,r-1)==gethash(l+1,r)) pp=1; printf("%d\n",pp); } }
|