P3538 题解
P3538
超弱省选题
给出一个由小写英文字母组成的字符串S,再给出q个询问,要求回答S某个子串的最短循环节。
其中\(|S|\le 5\times 10^5,q\le 10^6\)
分析:
首先:字符串\(S[l,r]\)拥有长为\(x\)的循环节的充要条件为:\(S[l,r-x]=S[l+x,r]\)。证明很简单,可以小思考一下。
那么显然任意循环节的长度一定是\(r-l+1\)的约数,这启发我们分解约数进行暴力枚举。
至于判断字符串相等,Hash即可,这时候你想到了一个结合Hash的枚举约数暴力算法,得到了\(90pts\)
#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
#define N 500050
int prime[N],vis[N],n,m;
unsigned long long hsh[N];
char a[N];
vector<int>d[N];
void find(){
for(int i=1;i<=n;i++){
for(int j=1;i*j<=n;j++){
d[i*j].push_back(i);
}
}
}
int power(unsigned long long a,int b){
unsigned long long ans=1;
while(b){
if(b&1)ans=ans*a;
a=a*a;
b>>=1;
}
return ans;
}
void init(){
cin>>n;
cin>>a+1;
cin>>m;
}
void Hash(){
for(int i=1;i<=n;i++){
hsh[i]=1ull*hsh[i-1]*131+(a[i]-'a');
}
}
int get(int l,int r){
return hsh[r]-hsh[l-1]*power(131ull,r-l+1);
}
int solve(){
int l,r;
cin>>l>>r;
int len=r-l+1;
int siz=d[len].size();
for(int i=0;i<siz;++i){
int x=d[len][i];
if(get(l,r-x)==get(l+x,r)){
return x;
}
}
return len;
}
int main(){
ios::sync_with_stdio(false);
init();
find();
Hash();
while(m--){
printf("%d\n",solve());
}
}
很明显,这个算法虽然平均复杂度是\(O((q+n)\log_2 n)\),但是可以被卡。
它的复杂度主要在于分解约数时。这启发我们考虑进一步缩小约数范围,也即若一个存在一个循环节,长度为\(x\),那么更小的循环节一定是\(x\)的约数,这启发我们从大到地去枚举循环节,具体是这样:
先我们来食用线性筛处理出每个数的最小质因数,那么因为约数也都是质因数组合而来,可以考虑对于最初的\(len=r-l+1\),枚举其所有的质因数(这一步可以用线性筛不断除以最小质因数,有几个幂就枚举几个),那么除掉之后若仍然是循环节,就将目前的答案除掉这个枚举的质因数,否则枚举下一个。显然这个方法最后求出来的答案是一个循环节,再者我们枚举了\(len\)所有的质因数,将不除掉的这些质因数除掉都会影响答案,那么不除掉的所有质因数的乘积就是答案。
所以答案正确,然后又因为我们枚举所有的质因数及其幂次,复杂度也是严格小于等于\(O(\log_2 n)\)的,故我们做到了严格的\(O(q\log_2 n)\)。
#define N 500050
int prime[N],vis[N],n,m,cnt;
unsigned long long hsh[N],p[N];
char a[N];
map<pair<int,int>,int>ans;
vector<int>d[N];
void find(){
vis[1]=1;
for(int i=2;i<=n;i++){
if(!vis[i]){
vis[i]=i;
prime[++cnt]=i;
}
for(int j=1;j<=cnt;j++){
if(prime[j]>vis[i]||prime[j]*i>n)break;
vis[prime[j]*i]=prime[j];
}
}
}
void init(){
cin>>n;
cin>>a+1;
cin>>m;
}
void Hash(){
p[0]=1;
for(int i=1;i<=n;i++){
hsh[i]=1ull*hsh[i-1]*131+(a[i]-'a');
p[i]=p[i-1]*131;
}
}
int get(int l,int r){
return hsh[r]-hsh[l-1]*p[r-l+1];
}
int solve(){
int l,r;
cin>>l>>r;
int tmp=r-l+1,lst=r-l+1;
if(get(l,r-1)==get(l+1,r))return 1;
while(vis[lst]>1){
if(get(l,r-tmp/vis[lst])==get(l+tmp/vis[lst],r)){
tmp=tmp/vis[lst];
}
lst/=vis[lst];
}
return tmp;
}
int main(){
ios::sync_with_stdio(false);
init();
find();
Hash();
while(m--){
printf("%d\n",solve());
}
}