[题解] [POI2012] OKR-A Horrible Poem
[题解] [POI2012]OKR-A Horrible Poem
字符串哈希题。
题目描述
给你一个字符串 \(S\)(\(|S|\leq 5e5\)) ,有 \(q\) (\(q\leq 2e6\))个询问,每个询问有一段区间 \((l,r)\) ,求出该子串 \(S_{i-j}\) 的最短循环元。
题解
前缀部分
如果有了循环节长度,如何利用哈希来判断这个长度的循环节合不合法。
只需要判断 \(S_{l,r-len}\) 和 \(S_{l+len,r}\) 是否相等即可,这不难证明是等价的。
KMP
仔细想想 KMP 只能处理前缀子串的循环元(\(i-next[i]\))
你也不能跑 \(n\) 次 KMP 吧
所以正解不可行。(不要一看循环元就想到 KMP)
hash
从浅层考虑:循环节长度必定是子串长度 \(len\) 的因数。
于是考虑枚举因数,复杂度 \(O(q\sqrt{n})\),会 TLE 。
所以考虑如何预处理来优化这个东西。
其次可以考虑一个性质:如果 \(c\) 是字符串 \(S\) 的一个非最大的循环节长度,那么如果 \(k*c\ |\ n\),(\(k\in Z\)) , \(k*c\) 也是一个合法的循环节长度。
所以逆过来想,不难对 \(len\) 进行质因子分解来依次考虑该循环节长度是否合法。
具体可以在线性筛的过程中利用每个数只被它最小的质因子筛去的性质,预处理出每个数的最小质因子 \(pri\) 数组。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
template <typename T>
inline T read(){
T x=0;char ch=getchar();bool fl=false;
while(!isdigit(ch)){if(ch=='-')fl=true;ch=getchar();}
while(isdigit(ch)){
x=(x<<3)+(x<<1)+(ch^48);ch=getchar();
}
return fl?-x:x;
}
const int maxn = 5e5 + 100;
char s[maxn];
#define LL long long
const LL base = 131LL , P = 1e9+7LL;
int n,q;
int primes[maxn],pri[maxn],cnt=0;
bool notp[maxn];
LL has[maxn],power[maxn];
void divide(int n){
notp[1]=true;
for(int i=2;i<=n;i++){
if(!notp[i])primes[++cnt]=i,pri[i]=i;
for(int j=1;j<=cnt;j++){
int tmp=primes[j]*i;
if(tmp>n)break;
notp[tmp]=true;pri[tmp]=primes[j];
if(i%primes[j]==0)break;
}
}
}
LL hashh(int l,int r){
return ((has[r]-has[l-1]*power[r-l+1]%P)+P)%P;
}
bool check(int l,int r,int len){
return hashh(l,r-len)==hashh(l+len,r);
}
int stk[maxn],top=0;
int main(){
n=read<int>();
cin>>(s+1);
q=read<int>();
divide(n+5);
power[0]=1;
for(int i=1;i<=n;i++){
has[i]=(has[i-1]*base%P+s[i]-'a'+1)%P;
power[i]=power[i-1]*base%P;
}
for(int i=1;i<=q;i++){
int l=read<int>(),r=read<int>();
int len=r-l+1,Len=len;
top=0;
while(Len>1){
stk[++top]=pri[Len];Len/=pri[Len];
}
for(int j=1;j<=top;j++)
if(check(l,r,len/stk[j]))len/=stk[j];
printf("%d\n",len);
}
return 0;
}
后记
感谢 @ww140142 。
增强了对线性筛和质因子分解的理解。