AC自动机(上)
-
-
简介:
-
前置芝士:KMP和Trie树。
-
本质:在Trie树上跑KMP。
-
一些abstract:
虽然基础是KMP,但是AC自动机和KMP不同,KMP是单模式串匹配算法,是提前预处理出模式串的“匹配规则”,这个匹配规则记录了当文本串和这个模式串相匹配的时候,如果失配了,到底要退到哪里继续进行匹配。
而AC自动机是要在多个模式串的情况下,判断文本串失配后该怎么继续匹配。如果对于一个文本串S和k个模式串Ti,求S中包含Ti的次数,如果分别跑KMP,复杂度是O(|S|k+|T1|+...+|Tk|);但是AC自动机可以保证O(|S|+|T1|+...+|Tk|)。
AC自动机是用多个模式串构建一棵字典树,然后在这个字典树上构建失配指针,而这个失配指针相当于KMP中的next数组,处理后,用文本串在这个字典树上进行模式匹配。
-
-
步骤:
先放板子:
namespace AC {
const int SZ = 100000+20;
int tot, tr[SZ][26];
// idx就是字典树中的节点是否是末尾,如果是值为串的id
// fail就是失配指针
// val是统计每个状态出现的次数
int fail[SZ], idx[SZ], val[SZ];
int cnt[N]; // 记录第 i 个字符串的出现次数
void init() {
memset(fail, 0, sizeof(fail));
memset(tr, 0, sizeof(tr));
memset(val, 0, sizeof(val));
memset(cnt, 0, sizeof(cnt));
memset(idx, 0, sizeof(idx));
tot = 0;
}
void insert(char *s, int id) { // id 表示原始字符串的编号
int u = 0;
for (int i = 1; s[i]; i++) {
if (!tr[u][s[i] - 'a']) tr[u][s[i] - 'a'] = ++tot;
u = tr[u][s[i] - 'a']; // 转移
}
idx[u] = id; // 以 u 为结尾的字符串编号为 idx[u]
}
queue<int> q;
void build() {
for (int i = 0; i < 26; i++)
if (tr[0][i]) q.push(tr[0][i]);
while (q.size()) {
int u = q.front();
q.pop();
for (int i = 0; i < 26; i++) {
if (tr[u][i]) {
fail[tr[u][i]] = tr[fail[u]][i]; // fail数组:同一字符可以匹配的其他位置
q.push(tr[u][i]);
} else
tr[u][i] = tr[fail[u]][i];
}
}
}
int query(char *t) { // 返回最大的出现次数
int u = 0, res =