参考文章AC自动机 感觉这篇文章讲的十分简洁易懂,但是好像有点地方的表述有点错误。我决定根据自己的理解再复述一次整个ac自动机的思路
AC自动机
ac自动机是在字典树和KMP算法的基础上进行拓展的,KMP算法属于是单模匹配算法,AC自动机属于是多模匹配算法。
原理
首先给定模式串"ash",“shex”,“bcd”,“sha” AC自动机就是在trie树的基础上,增加一个fail指针(相当于kmp中的next数组),如果当前点匹配失败,则将指针转移到fail指针指向的地方,这样就不用回溯,而可以路匹配下去了。
一般fail指针的构建是用bfs实现的,首先每个模式串的首字母肯定是指向根节点的。 现在第一层bfs遍历完了,开始第二层 (根节点为第0层)第二层a的子节点为s,但是我们还是要从a-z遍历,如果不存在这个子节点我们就让这个子节点指向当前节点fail指针的这个子节点) 当我们遍历当前节点子节点遍历到s的时候,由于存在s这个节点,我们就让s这个子节点的fail指针指向当前节点(a)的fail指针指向的那个节点的具有相同字母的子节点(第一层的s),也就是这样 后面的层规则与之前是相同的,以此类推即可。 完全构造好的树是这样的。 关于字符串匹配的过程就是跟kmp算法是一样的了,遇到适配字符就跳转到fail指针指向的位置即可。
模板
#include <bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
using namespace std;
const int N=2e6+6;
int trie[N][26];
int tot;
int cntword[N];
int fail[N];
void insertWords(string str)
{
int p=0;
for(int i = 0;i < str.size();i++)
{
int next = str[i]-'a';
if(trie[p][next]==0)
{
trie[p][next]=++tot;
}
p=trie[p][next];
}
cntword[tot]++;
}
void getFail()
{
queue<int> q;
for(int i=0;i < 26;i++)
{
if(trie[0][i])
{
fail[trie[0][i]]=0;
q.push(trie[0][i]);
}
}
while(!q.empty())
{
int now= q.front();
q.pop();
for(int i = 0;i < 26;i++)
{
if(trie[now][i]){
fail[trie[now][i]]=trie[fail[now]][i];
q.push(trie[now][i]);
}
else
{
trie[now][i]=trie[fail[now]][i];
}
}
}
}
int query(string s)
{
int now = 0,ans = 0;
for(int i=0;i<s.size();i++){
now = trie[now][s[i]-'a'];
for(int j=now;j && cntword[j]!=-1;j=fail[j]){
ans += cntword[j];
cntword[j] = -1;
}
}
return ans;
}
int main()
{
insertWords("in");
insertWords("name");
insertWords("na");
insertWords("string");
insertWords("int");
fail[0] = 0;
getFail();
cout << query("nameisinstring") << endl;
}
|