OI模板 AC自动机
trie 树上算失配指针,感觉和kmp没啥关系
简单版:
//P3808
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
struct AhoCorasick_Automaton{
struct node{ int ch[26], end, fail; } T[N];
int cnt;
void init(){
for(int i = 0; i < N; ++ i){
memset(T[i].ch, 0, sizeof(T[i].ch));
T[i].end = T[i].fail = 0;
}
cnt = 0;
}
void ins(char *s){
int len = strlen(s), now = 0;
for(int i = 0; i < len; ++ i){
char c = s[i] - 'a';
if(!T[now].ch[c]) T[now].ch[c] = ++cnt;
now = T[now].ch[c];
}
++ T[now].end;
}
void build(){
queue<int> q;
for(int i = 0; i < 26; ++ i) if(T[0].ch[i]) q.push(T[0].ch[i]);
while(!q.empty()){
int now = q.front(); q.pop();
for(int i = 0; i < 26; ++ i){
if(T[now].ch[i]){
T[T[now].ch[i]].fail = T[T[now].fail].ch[i];
q.push(T[now].ch[i]);
} else T[now].ch[i] = T[T[now].fail].ch[i];
}
}
}
int query(char *s){
build();
int len = strlen(s), now = 0, ans = 0;
for(int i = 0; i < len; ++ i){
now = T[now].ch[s[i]-'a'];
for(int i = now; i && T[i].end != -1; i = T[i].fail)
ans += T[i].end, T[i].end = -1;
}
return ans;
}
} AC;
char s[N]; int n;
int main(){
AC.init();
scanf("%d",&n);
for(int i=1; i<=n; ++i)
scanf("%s",s),
AC.ins(s);
scanf("%s",s);
printf("%d",AC.query(s));
return 0;
}
增强版:
名义上的增强版,实际的简单版
可以直接重复访问 T[i].end,虽然这样复杂度是错的,但是这题能过。
int query(char *s){
build();
int len = strlen(s), now = 0;
for(int i = 0; i < len; ++ i){
now = T[now].ch[s[i]-'a'];
for(int i = now; i; i = T[i].fail)
++ ans[T[i].end];
}
}
二次增强版:
复杂度错的怎么办?考虑仍然只遍历一次。
可以发现若开始跳 fail 一定是沿 fail 树往根跳,所以我们可以不跳,最后通过拓扑排序把答案传上去。
虽然题解说是个 DAG,但是我觉得还是棵树。
//P5357
#include <bits/stdc++.h>
using namespace std;
const int N = 2e6 + 10;
char s[N]; int n, ans[N];
struct Trie{
struct node{ int ch[26], end, val; } T[N];
int cnt;
void ins(char *s, int num){
int len = strlen(s), now = 0;
for(int i = 0; i < len; ++ i){
char c = s[i] - 'a';
if(!T[now].ch[c]) T[now].ch[c] = ++ cnt;
now = T[now].ch[c];
}
T[now].end = num;
}
int query(char *s){
int len = strlen(s), now = 0;
for(int i = 0; i < len; ++ i){
char c = s[i] - 'a';
if(!T[now].ch[c]) return 0;
now = T[now].ch[c];
}
return T[now].end;
}
} trie;
struct AhoCorasick_Automaton{
struct node{ int ch[26], end, fail, ans; } T[N];
int cnt, ind[N];
void ins(char *s, int num){
int len = strlen(s), now = 0;
for(int i = 0; i < len; ++ i){
char c = s[i] - 'a';
if(!T[now].ch[c]) T[now].ch[c] = ++cnt;
now = T[now].ch[c];
}
T[now].end = num;
}
void build(){
queue<int> q;
for(int i = 0; i < 26; ++ i) if(T[0].ch[i]) q.push(T[0].ch[i]);
while(!q.empty()){
int now = q.front(); q.pop();
for(int i = 0; i < 26; ++ i){
if(T[now].ch[i]){
T[T[now].ch[i]].fail = T[T[now].fail].ch[i];
q.push(T[now].ch[i]), ++ ind[T[T[now].fail].ch[i]];
} else T[now].ch[i] = T[T[now].fail].ch[i];
}
}
}
int query(char *s){
build();
int len = strlen(s), now = 0;
for(int i = 0; i < len; ++ i)
now = T[now].ch[s[i]-'a'], ++ T[now].ans;
queue<int> q;
for(int i = 0; i <= cnt; ++ i) if(!ind[i]) q.push(i);
while(!q.empty()){
int x = q.front(); q.pop(); ans[T[x].end] = T[x].ans;
int y = T[x].fail; -- ind[y], T[y].ans += T[x].ans;
if(!ind[y]) q.push(y);
}
}
} AC;
int main(){
scanf("%d", &n);
for(int i = 1; i <= n; ++ i){
scanf("%s", s);
int t = trie.query(s);
if(t) ans[i] = -t;
else trie.ins(s, i), AC.ins(s, i);
}
scanf("%s", s); AC.query(s);
for(int i = 1; i <= n; ++ i) printf("%d\n", ans[i]>=0 ? ans[i] : ans[-ans[i]]);
return 0;
}
AC自动机详解与实现
本文详细介绍AC自动机的基本概念及其实现方法,包括Trie树的构建与失配指针的计算,并提供了从简单版到增强版的不同实现示例。通过对AC自动机的深入剖析,帮助读者理解其在字符串匹配中的应用。
327

被折叠的 条评论
为什么被折叠?



