【Coel.解题报告】【阿霍-柯拉西克自动机!】AC自动机(简单版)
今天在鸽子大佬的帮助之下学会了AC自动机的基本操作,在此感谢!!!
你可以在史前找到我的血 2021/11/27 18:32:40
首先要构造一颗trie你可以在史前找到我的血 2021/11/27 18:33:23
然后构造fail指针 使当前字符失配时跳转到具有最长公共前后缀的字符继续匹配你可以在史前找到我的血 2021/11/27 18:33:33
就像KMP里面一样你可以在史前找到我的血 2021/11/27 18:33:36
失配指针你可以在史前找到我的血 2021/11/27 18:34:07
AC自动机当前字符匹配失败的话 就用fail指针跳转你可以在史前找到我的血 2021/11/27 18:34:54
跳转后的串的前缀 必定是跳转前的模式串的后缀你可以在史前找到我的血 2021/11/27 18:35:12
而且跳转新位置的深度 一定小于跳转之前的节点℃oel 2021/11/27 18:35:25
那fail怎么实现你可以在史前找到我的血 2021/11/27 18:35:37
所以这个fail指针 一般是利用bfs在trie上面求解你可以在史前找到我的血 2021/11/27 18:35:58
你可以在史前找到我的血
首先要构造一颗trie
首先说这个trie你可以在史前找到我的血 2021/11/27 18:36:05
需要三个指针你可以在史前找到我的血 2021/11/27 18:36:12
p p->fail temp你可以在史前找到我的血 2021/11/27 18:36:47
指针p指向当前匹配的字符串 如果p指向root 那就是当前匹配字符序列为空你可以在史前找到我的血 2021/11/27 18:36:56
(root是trie的入口你可以在史前找到我的血 2021/11/27 18:37:14
p->fail p的失败指针 指向和字符p相同的节点你可以在史前找到我的血 2021/11/27 18:37:39
temp 在建立fail指针时有寻找与p字符匹配的节点作用你可以在史前找到我的血 2021/11/27 18:37:55
对于trie树内的任意节点你可以在史前找到我的血 2021/11/27 18:38:09
对应一个序列s[1...m]你可以在史前找到我的血 2021/11/27 18:38:22
这个时候呢 p指向字符s[m]你可以在史前找到我的血 2021/11/27 18:38:30
如果在下一个字符失配你可以在史前找到我的血 2021/11/27 18:38:51
即p->next[s[m+1]] ==NLL你可以在史前找到我的血 2021/11/27 18:39:06
则由失配指针跳转到p->fail你可以在史前找到我的血 2021/11/27 18:39:17
对应的序列是s[i...m]你可以在史前找到我的血 2021/11/27 18:39:34
如果继续失配 就依次跳转到序列空或出现匹配你可以在史前找到我的血 2021/11/27 18:40:01
这个过程中p的值一直变化 但p对应节点的字符没有改变你可以在史前找到我的血 2021/11/27 18:40:19
可以求得序列s为最长公共后缀你可以在史前找到我的血 2021/11/27 18:40:29
由于这个序列是从root开始到某一节点,则说明这个序列有可能是某些序列的前缀你可以在史前找到我的血 2021/11/27 18:41:47
回到p指针上你可以在史前找到我的血 2021/11/27 18:41:59
如果p指针在某一字符s[m+1]失配你可以在史前找到我的血 2021/11/27 18:42:13
则说明没有单词s[1...m+1]存在你可以在史前找到我的血 2021/11/27 18:42:30
这个时候 如果失配指针指向root你可以在史前找到我的血 2021/11/27 18:42:31
则说明当前序列的任意后缀不会是某个单词的前缀你可以在史前找到我的血 2021/11/27 18:42:53
如果不指向root你可以在史前找到我的血 2021/11/27 18:42:59
则说明是你可以在史前找到我的血 2021/11/27 18:43:14
所以跳转到p的失配指针你可以在史前找到我的血 2021/11/27 18:44:07
以s[i...m]为前缀继续匹配s[m+1]你可以在史前找到我的血 2021/11/27 18:45:34
对于已经得到的序列s[1...m]由于s[i...m]可能是某个词的后缀 s[1...j]可能是某个词的前缀 所以s[1...m]可能存在词你可以在史前找到我的血 2021/11/27 18:46:05
但是这个时候 p已经指向已匹配的字符你可以在史前找到我的血 2021/11/27 18:46:16
所以令temp = p你可以在史前找到我的血 2021/11/27 18:46:35
之后依次测试s[1...m] s[i...m]是否为完整词你可以在史前找到我的血 2021/11/27 18:48:06
对于失配指针 可以用和KMP相似的思路你可以在史前找到我的血 2021/11/27 18:50:03
root入队 第一次循环处理和root相连的
P3808 【模板】AC自动机(简单版)
LOJ也有一道模板题,在此附上(多组数据注意)
#10057. 「一本通 2.4 例 1」Keywords Search
AC自动机由贝尔实验室的两位科学家Alfred V. Aho和Margaret J.Corasick于1975年发明(和KMP的发明时间十分相近),所以这个算法的名字以这两位大佬命名(Aho-Corasick Automaton)。
这个算法建立在一棵字典树上,同时利用一个失配函数(类似KMP的fail)简化搜索路径,所以又被我称为树上KMP。
实现过程如下:
- 把所有的模式串挂在字典树上(这一点和普通字典树一样)
- 通过深度优先搜索把所有的失配指针算出来
- 将文本串进行匹配
代码如下:
#include<cstdio>
#include<queue>
#include<cstring>
#define maxn 1000050
#define maxc 26
#define root 1
using namespace std;
int n,cnt=1;
char s[maxn];
queue<int>Q;
struct Aho_Corasick_Automaton
{
int leaf[maxc],tot,fail;
}trie[maxn];
inline void insert(char *s)//字典树的插字符串方式
{
int u=1,len=strlen(s);
for(register int i=0;i<len;i++)
{
int c=s[i]-'a';
if(!trie[u].leaf[c])
trie[u].leaf[c]=++cnt;
u=trie[u].leaf[c];
}
trie[u].tot++;
}
inline void getfail()//失配函数预处理
{
for(register int i=0;i<maxc;i++)
trie[0].leaf[i]=1;
Q.push(root);
trie[root].fail=0;
while(!Q.empty())
{
int u=Q.front();
Q.pop();
for(register int i=0;i<maxc;i++)
{
int c=trie[u].leaf[i],fail=trie[u].fail;
if(!c)
trie[u].leaf[i]=trie[fail].leaf[i];
else
{
trie[c].fail=trie[fail].leaf[i];
Q.push(c);
}
}
}
}
inline int query(char *s)//进行匹配
{
int u=root,ans=0,len=strlen(s);
for(register int i=0;i<len;i++)
{
int c=s[i]-'a',nxt=trie[u].leaf[c];
while(nxt>1&&trie[nxt].tot!=-1)
{
ans+=trie[nxt].tot;
trie[nxt].tot=-1;
nxt=trie[nxt].fail;
}
u=trie[u].leaf[c];
}
return ans;
}
int main()
{
scanf("%d",&n);
for(register int i=1;i<=n;i++)
{
scanf("%s",s);
insert(s);
}
getfail();
scanf("%s",s);
printf("%d",query(s));
return 0;
}