AC 自动机
概念讲解
什么是 AC 自动机?
AC 自动机是 KMP 算法和 Trie 的结合,用于多模式串匹配。
核心思想
- 构建 Trie 树
- 计算失配指针(类似 KMP 的 next 数组)
- 在 Trie 上进行匹配
实现
节点定义
123456789struct Node {
int children[26];
int fail;
int count;
};
const int N = 100005;
Node trie[N];
int idx = 0;构建 Trie
1234567891011void insert(string s) {
int p = 0;
for (char c : s) {
int u = c - 'a';
if (!trie[p].children[u]) {
trie[p].children[u] = ++idx;
}
p = trie[p].children[u];
}
trie[p].count++;
}构建失配指针
123456789101112131415161718192021222324void build() {
queue<int> q;
for (int i = 0; i < 26; i++) {
if (trie[0].children[i]) {
q.push(trie[0].children[i]);
}
}
while (!q.empty()) {
int u = q.front();
q.pop();
for (int i = 0; i < 26; i++) {
int v = trie[u].children[i];
if (v) {
trie[v].fail = trie[trie[u].fail].children[i];
q.push(v);
} else {
trie[u].children[i] = trie[trie[u].fail].children[i];
}
}
}
}匹配
123456789101112131415161718int match(string s) {
int p = 0;
int result = 0;
for (char c : s) {
int u = c - 'a';
p = trie[p].children[u];
int temp = p;
while (temp && trie[temp].count != -1) {
result += trie[temp].count;
trie[temp].count = -1;
temp = trie[temp].fail;
}
}
return result;
}应用场景
1. 多模式串匹配
1int count = match(text);2. 统计模式串出现次数
1// 在 match 函数中统计考点分析
初赛考查方式
- 选择题: AC 自动机的概念
- 程序阅读: 理解 AC 自动机
- 编程题: AC 自动机应用
常见题型
- 多模式串匹配
- 统计模式串出现次数
例题精讲
例题 1:多模式串匹配
题目: 在文本串中查找多个模式串出现的次数。
解答:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
struct Node {
int children[26];
int fail;
int count;
};
const int N = 100005;
Node trie[N];
int idx = 0;
void insert(string s) {
int p = 0;
for (char c : s) {
int u = c - 'a';
if (!trie[p].children[u]) {
trie[p].children[u] = ++idx;
}
p = trie[p].children[u];
}
trie[p].count++;
}
void build() {
queue<int> q;
for (int i = 0; i < 26; i++) {
if (trie[0].children[i]) {
q.push(trie[0].children[i]);
}
}
while (!q.empty()) {
int u = q.front();
q.pop();
for (int i = 0; i < 26; i++) {
int v = trie[u].children[i];
if (v) {
trie[v].fail = trie[trie[u].fail].children[i];
q.push(v);
} else {
trie[u].children[i] = trie[trie[u].fail].children[i];
}
}
}
}
int match(string s) {
int p = 0;
int result = 0;
for (char c : s) {
int u = c - 'a';
p = trie[p].children[u];
int temp = p;
while (temp && trie[temp].count != -1) {
result += trie[temp].count;
trie[temp].count = -1;
temp = trie[temp].fail;
}
}
return result;
}
int main() {
int n;
cin >> n;
for (int i = 0; i < n; i++) {
string s;
cin >> s;
insert(s);
}
build();
string text;
cin >> text;
cout << match(text) << endl;
return 0;
}练习题
- 洛谷 P3808 AC 自动机(简单版)
查看答案
思路:AC 自动机模板题。
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
struct Node {
int children[26];
int fail;
int count;
};
const int N = 1000005;
Node trie[N];
int idx = 0;
void insert(string s) {
int p = 0;
for (char c : s) {
int u = c - 'a';
if (!trie[p].children[u]) {
trie[p].children[u] = ++idx;
}
p = trie[p].children[u];
}
trie[p].count++;
}
void build() {
queue<int> q;
for (int i = 0; i < 26; i++) {
if (trie[0].children[i]) {
q.push(trie[0].children[i]);
}
}
while (!q.empty()) {
int u = q.front();
q.pop();
for (int i = 0; i < 26; i++) {
int v = trie[u].children[i];
if (v) {
trie[v].fail = trie[trie[u].fail].children[i];
q.push(v);
} else {
trie[u].children[i] = trie[trie[u].fail].children[i];
}
}
}
}
int match(string s) {
int p = 0;
int result = 0;
for (char c : s) {
int u = c - 'a';
p = trie[p].children[u];
int temp = p;
while (temp && trie[temp].count != -1) {
result += trie[temp].count;
trie[temp].count = -1;
temp = trie[temp].fail;
}
}
return result;
}
int main() {
int n;
cin >> n;
for (int i = 0; i < n; i++) {
string s;
cin >> s;
insert(s);
}
build();
string text;
cin >> text;
cout << match(text) << endl;
return 0;
}- 洛谷 P3796 AC 自动机(加强版)
查看答案
思路:AC 自动机,统计每个模式串出现的次数。
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
struct Node {
int children[26];
int fail;
int count;
int id;
};
const int N = 1000005;
Node trie[N];
int idx = 0;
int cnt[155];
void insert(string s, int id) {
int p = 0;
for (char c : s) {
int u = c - 'a';
if (!trie[p].children[u]) {
trie[p].children[u] = ++idx;
}
p = trie[p].children[u];
}
trie[p].count++;
trie[p].id = id;
}
void build() {
queue<int> q;
for (int i = 0; i < 26; i++) {
if (trie[0].children[i]) {
q.push(trie[0].children[i]);
}
}
while (!q.empty()) {
int u = q.front();
q.pop();
for (int i = 0; i < 26; i++) {
int v = trie[u].children[i];
if (v) {
trie[v].fail = trie[trie[u].fail].children[i];
q.push(v);
} else {
trie[u].children[i] = trie[trie[u].fail].children[i];
}
}
}
}
void match(string s) {
int p = 0;
for (char c : s) {
int u = c - 'a';
p = trie[p].children[u];
int temp = p;
while (temp) {
if (trie[temp].id) {
cnt[trie[temp].id] += trie[temp].count;
}
temp = trie[temp].fail;
}
}
}
int main() {
int n;
while (cin >> n && n) {
memset(trie, 0, sizeof(trie));
memset(cnt, 0, sizeof(cnt));
idx = 0;
string patterns[n + 1];
for (int i = 1; i <= n; i++) {
cin >> patterns[i];
insert(patterns[i], i);
}
build();
string text;
cin >> text;
match(text);
int maxCnt = 0;
for (int i = 1; i <= n; i++) {
maxCnt = max(maxCnt, cnt[i]);
}
cout << maxCnt << endl;
for (int i = 1; i <= n; i++) {
if (cnt[i] == maxCnt) {
cout << patterns[i] << endl;
}
}
}
return 0;
}- 洛谷 P5357 AC 自动机(二次加强版)
查看答案
思路:AC 自动机 + 拓扑排序优化。
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
struct Node {
int children[26];
int fail;
int count;
int id;
};
const int N = 200005;
Node trie[N];
int idx = 0;
int cnt[N], inDegree[N];
void insert(string s, int id) {
int p = 0;
for (char c : s) {
int u = c - 'a';
if (!trie[p].children[u]) {
trie[p].children[u] = ++idx;
}
p = trie[p].children[u];
}
trie[p].count++;
trie[p].id = id;
}
void build() {
queue<int> q;
for (int i = 0; i < 26; i++) {
if (trie[0].children[i]) {
q.push(trie[0].children[i]);
}
}
while (!q.empty()) {
int u = q.front();
q.pop();
for (int i = 0; i < 26; i++) {
int v = trie[u].children[i];
if (v) {
trie[v].fail = trie[trie[u].fail].children[i];
inDegree[trie[v].fail]++;
q.push(v);
} else {
trie[u].children[i] = trie[trie[u].fail].children[i];
}
}
}
}
void match(string s) {
int p = 0;
for (char c : s) {
int u = c - 'a';
p = trie[p].children[u];
cnt[p]++;
}
}
void topoSort() {
queue<int> q;
for (int i = 1; i <= idx; i++) {
if (inDegree[i] == 0) {
q.push(i);
}
}
while (!q.empty()) {
int u = q.front();
q.pop();
cnt[trie[u].fail] += cnt[u];
inDegree[trie[u].fail]--;
if (inDegree[trie[u].fail] == 0) {
q.push(trie[u].fail);
}
}
}
int main() {
int n;
cin >> n;
string patterns[n + 1];
for (int i = 1; i <= n; i++) {
cin >> patterns[i];
insert(patterns[i], i);
}
build();
string text;
cin >> text;
match(text);
topoSort();
for (int i = 1; i <= n; i++) {
int p = 0;
for (char c : patterns[i]) {
int u = c - 'a';
p = trie[p].children[u];
}
cout << cnt[p] << endl;
}
return 0;
}易错点
⚠️ 注意:
- AC 自动机是 KMP 和 Trie 的结合
- 失配指针的构建是关键
- 匹配时要注意失配指针的跳转
- AC 自动机是字符串算法的高级内容