AC自动机
下面是一份比赛常用的 AC 自动机 C++ 模板,默认字符集为小写字母 'a' ~ 'z',支持重复模式串,查询每个模式串在文本中的出现次数(可重叠)。
#include <bits/stdc++.h>
using namespace std;
struct AhoCorasick {
static constexpr int SIGMA = 26;
vector<array<int, SIGMA>> nxt; // Trie 转移
vector<int> fail; // fail 指针
vector<int> cnt; // 状态访问次数 / 最后作为出现次数
vector<int> ord; // BFS 序,用于逆序合并
vector<int> pos; // 每个模式串的结束节点
AhoCorasick() { init(); }
explicit AhoCorasick(int maxNode) { init(maxNode); }
void init(int maxNode = 0) {
nxt.clear();
fail.clear();
cnt.clear();
ord.clear();
pos.clear();
if (maxNode > 0) {
nxt.reserve(maxNode + 1);
fail.reserve(maxNode + 1);
cnt.reserve(maxNode + 1);
ord.reserve(maxNode + 1);
}
// 根节点为 0
nxt.push_back({});
nxt.back().fill(0);
fail.push_back(0);
cnt.push_back(0);
}
int newNode() {
nxt.push_back({});
nxt.back().fill(0);
fail.push_back(0);
cnt.push_back(0);
return (int)nxt.size() - 1;
}
// 插入模式串,返回它的编号
int insert(const string& s) {
int u = 0;
for (char ch : s) {
int c = ch - 'a';
if (nxt[u][c] == 0) {
nxt[u][c] = newNode();
}
u = nxt[u][c];
}
pos.push_back(u);
return (int)pos.size() - 1;
}
// 构建 fail 指针,并补全 Trie 转移
void build() {
queue<int> q;
// 根节点的子节点
for (int c = 0; c < SIGMA; ++c) {
int v = nxt[0][c];
if (v) {
fail[v] = 0;
q.push(v);
}
}
ord.clear();
while (!q.empty()) {
int u = q.front();
q.pop();
ord.push_back(u);
for (int c = 0; c < SIGMA; ++c) {
int v = nxt[u][c];
if (v) {
fail[v] = nxt[fail[u]][c];
q.push(v);
} else {
// 补全转移,匹配时可以 O(1) 跳转
nxt[u][c] = nxt[fail[u]][c];
}
}
}
}
// 统计文本 t 中每个模式串的出现次数
// 调用后用 occurrences(i) 查询第 i 个模式串
void match(const string& t) {
fill(cnt.begin(), cnt.end(), 0);
int u = 0;
for (char ch : t) {
int c = ch - 'a';
u = nxt[u][c];
++cnt[u];
}
// 逆 BFS 序,沿 fail 树向上合并
for (int i = (int)ord.size() - 1; i >= 0; --i) {
int u = ord[i];
cnt[fail[u]] += cnt[u];
}
}
// 第 id 个模式串的出现次数
int occurrences(int id) const {
return cnt[pos[id]];
}
// 第 id 个模式串是否出现
bool exists(int id) const {
return occurrences(id) > 0;
}
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
AhoCorasick ac;
for (int i = 0; i < n; ++i) {
string s;
cin >> s;
ac.insert(s);
}
ac.build();
string text;
cin >> text;
ac.match(text);
for (int i = 0; i < n; ++i) {
cout << ac.occurrences(i) << '\n';
}
return 0;
}
AC 自动机利用一个 fail 指针来辅助多模式串的匹配.
fail 指针与 KMP 中的 next 指针相比:
共同点:两者同样是在失配的时候用于跳转的指针.
不同点:next 指针求的是最长 Border(即最长的相同前后缀),而 fail 指针指向所有模式串的前缀中匹配当前状态的最长后缀.
因为 KMP 只对一个模式串做匹配,而 AC 自动机要对多个模式串做匹配.有可能 fail 指针指向的结点对应着另一个模式串,两者前缀不同.
总结下来,AC 自动机的失配指针指向当前状态的最长后缀状态.
注意:AC 自动机在做匹配时,同一位上可匹配多个模式串.
i he his her hers

haze

浙公网安备 33010602011771号