【Codeforces235C】Cyclical Quest 后缀自动机
C. Cyclical Quest
Some days ago, WJMZBMR learned how to answer the query "how many times does a string x occur in a string s" quickly by preprocessing the string s. But now he wants to make it harder.
So he wants to ask "how many consecutive substrings of s are cyclical isomorphic to a given string x". You are given string s and nstrings xi, for each string xi find, how many consecutive substrings of s are cyclical isomorphic to xi.
Two strings are called cyclical isomorphic if one can rotate one string to get the other one. 'Rotate' here means 'to take some consecutive chars (maybe none) from the beginning of a string and put them back at the end of the string in the same order'. For example, string "abcde" can be rotated to string "deabc". We can take characters "abc" from the beginning and put them at the end of "de".
Input
The first line contains a non-empty string s. The length of string s is not greater than 106 characters.
The second line contains an integer n (1 ≤ n ≤ 105) — the number of queries. Then n lines follow: the i-th line contains the string xi — the string for the i-th query. The total length of xi is less than or equal to 106 characters.
In this problem, strings only consist of lowercase English letters.
Output
For each query xi print a single integer that shows how many consecutive substrings of s are cyclical isomorphic to xi. Print the answers to the queries in the order they are given in the input.
Examples
baabaabaaa
5
a
ba
baa
aabaa
aaba
7
5
7
3
5
aabbaa
3
aa
aabb
abba
2
3
3
Solution
题目大意:给出一个字符串T,再给出N个字符串S,每次回答字符串S的所有循环串在T中出现次数,重复的不计入答案. 这里的循环串的意思 每次将首位字符置于末位置后形成的新串.
后缀自动机
先对模板串建后缀自动机,然后对于询问的每个串分别放到自动机上匹配,并统计答案.
具体的过程就是,对于询问的每个串S,构建成串SS,然后放到自动机上匹配,匹配的方法和求LCS时类似,如果匹配到的位置$>N$且匹配的长度$>=N$那么说明匹配出了一种循环串,这时候可以考虑统计这个循环串的答案.
就是在询问之前拓扑排序递推出每个节点的贡献,然后如果此时匹配到的点在Parent树中的节点刚好能够表示长度为N的子串,那么直接累加贡献即可,否则沿Parent指针跳到合适的节点再累加贡献即可.
这样累加贡献显然会出现重复的情况,那么只需要在每个节点上再打上一个标记,如果此次查询未利用这个节点的贡献,则计入贡献,否则跳过,即可得解.
Code
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> using namespace std; #define MAXN 1000010 char S[MAXN<<1]; int N,Q; namespace SAM { int son[MAXN<<1][27],len[MAXN<<1],par[MAXN<<1],size[MAXN<<1]; int last,sz=1,root=1; inline void Init() {last=root;} inline void Extend(int c) { int cur=++sz,p=last; len[cur]=len[p]+1; size[cur]=1; while (p && !son[p][c]) son[p][c]=cur,p=par[p]; if (!p) par[cur]=root; else { int q=son[p][c]; if (len[p]+1==len[q]) par[cur]=q; else { int nq=++sz; memcpy(son[nq],son[q],sizeof(son[nq])); len[nq]=len[p]+1; par[nq]=par[q]; while (p && son[p][c]==q) son[p][c]=nq,p=par[p]; par[cur]=par[q]=nq; } } last=cur; } inline void Build() {Init(); for (int i=1; i<=N; i++) Extend(S[i]-'a'+1);} int st[MAXN],id[MAXN<<1]; inline void Pre() { for (int i=1; i<=sz; i++) st[len[i]]++; for (int i=1; i<=N; i++) st[i]+=st[i-1]; for (int i=1; i<=sz; i++) id[st[len[i]]--]=i; for (int i=sz; i>=1; i--) size[par[id[i]]]+=size[id[i]]; } int flag[MAXN<<1]; inline void Query() { int now=root,L=0,ans=0; for (int i=1; i<=N+N; i++) { int c=S[i]-'a'+1; if (son[now][c]) L++,now=son[now][c]; else { while (now && !son[now][c]) now=par[now]; if (!now) now=root,L=0; else L=len[now]+1,now=son[now][c]; } if (i>N && L>=N) { int tmp=now; while (tmp && !(N>=len[par[tmp]]+1 && N<=len[tmp])) tmp=par[tmp]; if (!tmp) tmp=root; if (flag[tmp]!=Q+1) ans+=size[tmp],flag[tmp]=Q+1; } } printf("%d\n",ans); } }using namespace SAM; int main() { scanf("%s",S+1); N=strlen(S+1); SAM::Build(); SAM::Pre(); scanf("%d",&Q); while (Q--) { scanf("%s",S+1); N=strlen(S+1); for (int i=1; i<=N; i++) S[N+i]=S[i]; SAM::Query(); } return 0; }