CF235C-Cyclical Quest
题目
开始给出母串,多次询问一个串的所有不同循环串的在母串中的出现总次数。母串长和询问总长小于等于\(10^6\) 。
Input
baabaabaaa
5
a
ba
baa
aabaa
aaba
Output
7
5
7
3
5
分析
设计到子串的问题我们可以考虑后缀自动机。
问题就变成如何在后缀自动机中连续地匹配一个串的循环串。设需要匹配的串长为\(n\),我们首先把它倍长,放进自动机里匹配。如果匹配长度大于等于\(n\)就说明这个开头的位置匹配成功了。这时我们跳link,直到再跳一次就会令匹配长度小于\(n\),即当前点的出现次数就是这个循环串的出现次数,因为自动机的link相当于是在前面截掉一段。这样就可以\(O(n)\)解决每个匹配了。关于跳link的次数,每次跳link长度都会减一,而长度最多累加到\(2n\),所以得到这个复杂度。
后缀自动机的link是把前面一段截掉,这个性质很有用。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=2e6+10; // 2e6+10
const int maxc=26;
char s[maxn];
struct SAM {
int t[maxn][maxc],len[maxn],link[maxn],tot,last;
int ord[maxn],sum[maxn],tim[maxn],tic;
bool spe[maxn];
SAM ():tot(1),last(1),tic(0) {}
void add(int x) {
int nw=++tot,i;
spe[nw]=true;
len[nw]=len[last]+1;
for (i=last;i && !t[i][x];i=link[i]) t[i][x]=nw;
if (i) {
int p=t[i][x];
if (len[p]==len[i]+1) link[nw]=p; else {
int q=++tot;
len[q]=len[i]+1;
memcpy(t[q],t[p],sizeof t[p]);
for (int j=i;j && t[j][x]==p;j=link[j]) t[j][x]=q;
link[q]=link[p],link[p]=link[nw]=q;
}
} else link[nw]=1;
last=nw;
}
void prepare() {
for (int i=1;i<=tot;++i) ++sum[len[i]];
for (int i=1;i<=tot;++i) sum[i]+=sum[i-1];
for (int i=tot;i;--i) ord[sum[len[i]]--]=i;
memset(sum,0,sizeof sum);
for (int i=tot;i>1;--i) sum[link[ord[i]]]+=(sum[ord[i]]+=spe[ord[i]]);
sum[0]=sum[1]=0;
}
int run(char s[],int n) {
int ret=0,now=1,mat=0;
++tic;
for (int i=1;i<(n<<1);++i) {
int x=s[i]-'a';
while (now!=1 && !t[now][x]) mat=len[now=link[now]];
if (t[now][x]) now=t[now][x],++mat;
while (len[link[now]]>=n) mat=len[now=link[now]];
if (mat>=n && tim[now]!=tic) tim[now]=tic,ret+=sum[now];
}
return ret;
}
} sam;
int main() {
#ifndef ONLINE_JUDGE
freopen("test.in","r",stdin);
#endif
scanf("%s",s+1);
int len=strlen(s+1);
for (int i=1;i<=len;++i) sam.add(s[i]-'a');
sam.prepare();
int m;
scanf("%d",&m);
while (m--) {
scanf("%s",s+1),len=strlen(s+1);
memcpy(s+len+1,s+1,(sizeof s[0])*len);
s[len<<1|1]='\0';
int ans=sam.run(s,len);
printf("%d\n",ans);
}
return 0;
}