【CF235C】Cyclical Quest
题目
题目链接:https://codeforces.com/problemset/problem/235/C
给定一个主串 \(S\) 和 \(n\) 个询问串,求每个询问串的所有循环同构在主串中出现的次数总和。
\(n\leq 10^5,|S|,\sum|T|\leq 10^6\)。
思路
考虑字符串 \(abcde\) 将 \(a\) 移动到最后一位时,实际上就是先将第一位删除,再添加一个新的字符。
而我们知道 SAM 的 parent 树的父子关系为在字符串前添加若干个字符,自动机上两个相邻的点等价于在字符串后面加上一个字符。所以我们考虑用 SAM 解决此题。
我们先枚举 \(T\) 的每一位,如果当前节点存在一个儿子为这个字符,那么就跳到这个字符;否则不断往 parent 树上的父亲跳。
同时维护一个变量 \(cnt\),表示当前字符串与自动机上的子串匹配了多少位。
然后再次枚举 \(T\),考虑把当前的字符移动到最后。如果此时 \(cnt\) 等于询问串的长度,那么显然删除后匹配的位数会减一,因为被我们删除的第一位本来是匹配的。
然后因为我们删除了一个字符,我们需要判断它的 \(\mathrm{endpos}\) 集合有没有改变(也就是匹配的长度是否等于了当前节点的父节点的长度),如果有,那么就往它的父节点跳。
接下来重复寻找下一个含有字符 \(c\) 的过程,然后继续维护好 \(cnt\) 即可。如果一次移动之后 \(cnt\) 等于询问串长度,那么就要加上位置不同的子串数量。这个很好预处理。
注意因为循环同构要求互不相同,所以我们要在 SAM 上遍历过的节点打上 tag。
时间复杂度 \(O(n)\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=2000010;
int Q,n,m,vis[N],b[N],a[N];
char s[N],t[N];
struct SAM
{
int last,tot,fa[N],ch[N][26],siz[N],len[N];
SAM() { last=tot=1; }
void ins(int c)
{
int p=last,np=++tot;
last=np; len[np]=len[p]+1; siz[np]=1;
for (;p && !ch[p][c];p=fa[p]) ch[p][c]=np;
if (!p) fa[np]=1;
else
{
int q=ch[p][c];
if (len[q]==len[p]+1) fa[np]=q;
else
{
int nq=++tot;
len[nq]=len[p]+1; fa[nq]=fa[q];
for (int i=0;i<26;i++) ch[nq][i]=ch[q][i];
fa[q]=fa[np]=nq;
for (;p && ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
}
}
}
void topsort()
{
for (int i=1;i<=tot;i++) b[len[i]]++;
for (int i=1;i<=tot;i++) b[i]+=b[i-1];
for (int i=1;i<=tot;i++) a[b[len[i]]--]=i;
for (int i=tot;i>=1;i--) siz[fa[a[i]]]+=siz[a[i]];
}
void solve(char *s)
{
m=strlen(s+1);
int cnt=0,p=1,ans=0;
for (int i=1;i<=m;i++)
{
int id=s[i]-'a';
while (p && !ch[p][id])
p=fa[p],cnt=len[p];
if (!p) p=1,cnt=0;
else cnt++,p=ch[p][id];
}
if (cnt==m)
{
cnt--;
if (fa[p] && len[fa[p]]==cnt) p=fa[p];
}
for (int i=1;i<=m;i++)
{
int id=s[i]-'a';
while (p && !ch[p][id])
p=fa[p],cnt=len[p];
if (!p) p=1,cnt=0;
else
{
cnt++; p=ch[p][id];
if (cnt==m)
{
if (vis[p]>Q) vis[p]=Q,ans+=siz[p];
cnt--;
if (fa[p] && len[fa[p]]==cnt) p=fa[p];
}
}
}
printf("%d\n",ans);
}
}sam;
int main()
{
scanf("%s%d",s+1,&Q);
n=strlen(s+1);
for (int i=1;i<=n;i++)
sam.ins(s[i]-'a');
sam.topsort();
memset(vis,0x3f3f3f3f,sizeof(vis));
while (Q--)
{
scanf("%s",t+1);
sam.solve(t);
}
return 0;
}