AC自动机学习笔记总结
程序员文章站
2024-01-29 13:56:22
...
离第一次讲这个算法已经过去大半年了,我才来填这个坑,蒟蒻无疑了… …
言归正传,这两周认真复习 (开始学习) AC自动机,敲了几遍板子,感觉自己基本上理解了这个算法,所以来写一篇学习笔记,以便将来再次复习 (开始学习)。
注:此篇博客仅作为将来复习时用,他人学习请另选高人博客。
前置知识
- KMP
- Trie树
简介
处理多字符串匹配问题,常规问题如:给出n个单词,再给出一段包含m个字符的文章S,问有多少个单词在文章里出现了以及出现的次数。
思想
- 用Trie树存模式串。
- 找每个节点的指针,并找出每个节点的值。
- 跑一遍文章字符串,统计个数即可。
重点!!!
我们知道:当节点u匹配失败时,在所有给定的单词中,我们需要找到另一个单
词v,使得它有尽量长的前缀与u的父节点所代表的串(不含u)的后缀相等,令。
那如何求指针?
首先初始化,将根节点的每个子节点的都指向根节点,即。
对于其他节点,令这个节点为,我们便沿着它父节点的指针走,一直递归,直到找到一个节点,它的子节点中存在字符,我们就令。
板子
有一个由n个单词组成的单词库(仅含小写英文字母),给出一篇长度(即字符个数)
为m的文章,请你计算:单词库中有几个单词在文章中出现过。
第1行1个整数n;
在接下来的n行中,每行输入1个单词;
最后一行输入一个字符串,表示文章。
样例输入:
5
she
he
say
shr
her
yasherhs
输出:
3
#include<bits/stdc++.h>
using namespace std;
int tr[1010][30],fail[1000005],last[1000005],sz,n,val[1000005];
char t[1005],s[1000004];
void build(char *t)
{
int n=strlen(t),u=0;
for(int i=0;i<n;i++)
{
int c=t[i]-'a';
if(!tr[u][c]) tr[u][c]=sz++;
u=tr[u][c];
}
val[u]++;
}
void getfail()
{
queue<int>q;
fail[0]=0;
int u=0;
for(int i=0;i<26;i++)
{
u=tr[0][i];
if(u) {q.push(u);fail[u]=0;last[u]=0;}
}
while(!q.empty())
{
int r=q.front();q.pop();
for(int i=0;i<26;i++)
{
u=tr[r][i];
if(!u)
{
tr[r][i]=tr[fail[r]][i];
continue;
}
q.push(u);
int v=fail[r];
while(v&&!tr[v][i]) v=fail[v];
fail[u]=tr[v][i];
last[u]=val[fail[u]] ? fail[u] : last[fail[u]];
}
}
}
int find(char *s)
{
int u=0,cnt=0;
n=strlen(s);
for(int i=0;i<n;i++)
{
int c=s[i]-'a';
u=tr[u][c];
int temp=0;
if(val[u]) temp=u;
else if(last[u]) temp=last[u];
while(temp)
{
cnt+=val[temp];
val[temp]=0;
temp=last[temp];
}
}
return cnt;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%s",t);
build(t);
}
getfail();
scanf("%s",s);
int ans=find(s);
printf("%d",ans);
return 0;
}
撒花完结!!!