【模版】AC自动机
可以在文本串中查找多个模式串。
前置知识点:
KMP 算法
Trie 树
开了一个
f
a
i
l
[
i
]
fail[i]
fail[i] 数组(存的是
t
r
i
e
trie
trie 树上的结点号)
表示
t
r
i
e
trie
trie 树 某个结点 在 文本串上 第
t
r
i
e
[
i
]
[
j
]
trie[i][j]
trie[i][j] 号节点匹配上时,跳至 与当前所在 模式串 的后缀有 公共前缀的 下一个 模式串 。跳至的目标就是
f
a
i
l
[
i
]
fail[i]
fail[i]
比如两个模式串
a
b
c
d
abcd
abcd 和
a
b
c
abc
abc 。 在访问
a
b
c
d
abcd
abcd 时的
c
c
c 时,会跳去判断第二个字符串
a
b
c
abc
abc 因为
a
b
c
d
abcd
abcd 前三个字母 是
a
b
c
abc
abc 的后三个字符,符合前缀后缀相同的关系。
T
r
i
e
Trie
Trie 树的建立没有变化,不过建立的是模式串的
T
r
i
e
Trie
Trie 树 。
f
a
i
l
[
]
fail[]
fail[] 数组需要提前预处理:
按层数对
t
r
i
e
trie
trie 树进行
b
f
s
bfs
bfs , 对于每一个被遍历到点(无论是否存在子节点),首先判断其子节点是否是另一个模式串的前缀部分。
如果是(则说明其有子结点),则其子节点的
f
a
i
l
[
]
fail[]
fail[] 数组 保存 与该模式串的后缀 有 相同前缀的 结点编号。
如果否(则说明其无子节点),则 令其 新创立
t
r
i
e
[
i
]
[
j
]
trie[i][j]
trie[i][j], 保存当前点 的
f
a
i
l
[
]
fail[]
fail[] 数组 所记录点的 子结点。
void Set_fail()
{
for(int i = 0; i < 26; i++)
if(trie[0][i])
q.push(trie[0][i]);
while(!q.empty())
{
int k = q.front();
q.pop();
for(int i = 0; i < 26; i++)
if(trie[k][i])
{
fail[trie[k][i]] = trie[fail[k]][i];
q.push(trie[k][i]);
}
else trie[k][i] = trie[fail[k]][i];
}
}
当在文本串上查询模式串的时候,每当遍历到一个结点,跳至其 f a i l [ ] fail[] fail[] 结点,如果某一模式串以该结点为结尾,则匹配成功。
int Query(char st[])
{
int k = 0, ans = 0, len = strlen(st);
for(int i = 0; i < len; i++)
{
k = trie[k][st[i]-'a'];
for(int j = k; j && rt[j] != -1; j = fail[j])
ans += rt[j], rt[j] = -1;
}
return ans;
}
例题:
AC自动机[简单]
代码:
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
#include <iostream>
using namespace std;
const int Maxn = 1000100;
queue <int> q;
int n,cnt;
char t[Maxn];
int trie[Maxn][27], fail[Maxn], rt[Maxn];
void Insert(char st[])
{
int len = strlen(st), k = 0;
for(int i = 0; i < len; i++)
{
int c = st[i] - 'a';
if(!trie[k][c]) trie[k][c] = ++cnt;
k = trie[k][c];
}
rt[k]++;
}
void Set_fail()
{
for(int i = 0; i < 26; i++)
if(trie[0][i])
q.push(trie[0][i]);
while(!q.empty())
{
int k = q.front();
q.pop();
for(int i = 0; i < 26; i++)
if(trie[k][i])
{
fail[trie[k][i]] = trie[fail[k]][i];
q.push(trie[k][i]);
}
else trie[k][i] = trie[fail[k]][i];
}
}
int Query(char st[])
{
int k = 0, ans = 0, len = strlen(st);
for(int i = 0; i < len; i++)
{
k = trie[k][st[i]-'a'];
for(int j = k; j && rt[j] != -1; j = fail[j])
ans += rt[j], rt[j] = -1;
}
return ans;
}
int main()
{
scanf("%d",&n);
for(int i = 1; i <= n; i++)
{
scanf("%s",t);
t[strlen(t)] = '\0';
Insert(t);
}
Set_fail();
scanf("%s",t);
t[strlen(t)] = '\0';
printf("%d\n",Query(t));
return 0;
}
AC自动机[加强]
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <stack>
#include <queue>
#include <ctime>
#include <set>
using namespace std;
const int Maxn = 12000;
const int Maxs = 70 + 10;
queue <int> q;
int n, cnt;
int trie[Maxn][30], fail[Maxn], rt[Maxn], app[Maxn];
char t[160][Maxs], key[1000100];
void Insert(char st[], int id)
{
int len = strlen(st), k = 0;
for(int i = 0; i < len; i++)
{
int c = st[i] - 'a';
if(!trie[k][c]) trie[k][c] = ++cnt;
k = trie[k][c];
}
rt[k] = id;
}
void Set_fail(char st[])
{
int len = strlen(st), k = 0;
for(int i = 0; i < 26; i++)
if(trie[0][i])
q.push(trie[0][i]);
while(!q.empty())
{
int k = q.front();
q.pop();
for(int i = 0; i < 26; i++)
if(trie[k][i])
{
fail[trie[k][i]] = trie[fail[k]][i];
q.push(trie[k][i]);
}
else trie[k][i] = trie[fail[k]][i];
}
}
void Pre_work(char st[])
{
int len = strlen(st), k = 0;
for(int i = 0; i < len; i++)
{
k = trie[k][st[i]-'a'];
for(int j = k; j; j = fail[j])
if(rt[j])
app[rt[j]]++;
}
}
int main()
{
scanf("%d",&n);
while(n)
{
cnt = 0;
memset(trie, 0, sizeof(trie));
memset(fail, 0, sizeof(fail));
memset(app, 0, sizeof(app));
memset(rt, 0, sizeof(rt));
for(int i = 1; i <= n; i++)
{
scanf("%s",t[i]);
t[i][strlen(t[i])] = '\0';
Insert(t[i], i);
}
scanf("%s",key);
Set_fail(key);
Pre_work(key);
int ans = 0;
for(int i = 1; i <= n; i++) ans = max(ans, app[i]);
printf("%d\n",ans);
for(int i = 1; i <= n; i++) if(app[i] == ans) printf("%s\n",t[i]);
cin >> n;
}
system("pause");
return 0;
}