题目
题目链接:https://www.luogu.com.cn/problem/P3808
给定 \(n\) 个模式串 \(s_i\) 和一个文本串 \(t\),求有多少个不同的模式串在文本串里出现过。
两个模式串不同当且仅当他们编号不同。
思路
AC 自动机可以看做 KMP + Trie。
首先我们将所有模式串建成一棵 Trie 树,定义 \(fail[i]\) 表示一个点 \(i\) 到根路径形成的字符串,最长出现在 Trie 树中的后缀的位置。其定义可以类比 KMP 的 next 数组。
记 \(c[i][x]\) 表示:
- 如果点 \(i\) 有字符 \(x\) 儿子,那么为这个儿子的编号。
- 否则为点 \(i\) 到根形成的字符串最长拥有 \(x\) 儿子的后缀的 \(x\) 儿子编号。
显然 \(fail\) 会形成一个树形结构,我们可以构建出 fail 树,然后在文本串匹配到点 \(i\) 的时候不断往其 \(fail\) 跳,并将答案加上到达的点结尾的模式串数量。
注意要类比并查集路径压缩的方法处理 \(c\) 数组。
显然每个点只会遍历一遍,时间复杂度 \(O(\sum^{n}_{i=1}len_i)\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=1000010,M=27;
int n,len;
char s[N];
struct ACA
{
int tot,c[N][M],num[N],fail[N];
bool vis[N];
void ins(char *ch)
{
int len=strlen(ch+1),p=0;
for (int i=1;i<=len;i++)
{
int val=ch[i]-'a'+1;
if (!c[p][val]) c[p][val]=++tot;
p=c[p][val];
}
num[p]++;
}
void build()
{
queue<int> q;
for (int i=1;i<=26;i++)
if (c[0][i]) q.push(c[0][i]);
while (q.size())
{
int u=q.front();
q.pop();
for (int i=1;i<=26;i++)
if (c[u][i]) fail[c[u][i]]=c[fail[u]][i],q.push(c[u][i]);
else c[u][i]=c[fail[u]][i];
}
}
int query(char *ch)
{
int len=strlen(ch+1),p=0,ans=0;
for (int i=1;i<=len;i++)
{
int val=ch[i]-'a'+1;
p=c[p][val];
for (int j=p;j && !vis[j];j=fail[j])
ans+=num[j],vis[j]=1;
}
return ans;
}
}AC;
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%s",s+1);
AC.ins(s);
}
AC.build();
scanf("%s",s+1);
printf("%d",AC.query(s));
return 0;
}