题目链接:【模板】AC 自动机(简单版) - 洛谷
代码:
#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
using namespace std;
const int N=1000005;
int ch[N][26],fail[N],tot,en[N];
inline void build(const string&s){ //普通的trie树build函数
int len=s.size(), x, u=0;
for(int i=0; i<len; i++){
x=s[i]-'a';
if(!ch[u][x]) ch[u][x] = ++tot; //如果没有这个点,就建立
u=ch[u][x];
}
en[u]++; //记录出现次数
}
inline void get_fail(){ //ac自动机的精髓,求解fail数组
queue<int> q;
for(int i=0; i<26; i++) //特别处理第一层
if(ch[0][i]) q.push(ch[0][i]); //第一层结点加入队列
//注意:所有第一层结点的fail值都没有处理过,也就是默认他们的fail值都是0,回到root点
while(q.size()){ //bfs求fail指针
int u=q.front(); q.pop(); //父结点
for(int i=0; i<26; i++){
if(ch[u][i]){ //有这个子结点
fail[ch[u][i]] = ch[fail[u]][i]; //核心步骤,fail = fa->fail->child
q.push(ch[u][i]);
} else {ch[u][i] = ch[fail[u]][i];} //特殊处理,建立虚拟子结点,实际上相当于失配
}
}
}
int solve(const string&s){
int u=0, res=0;
for(char c:s){ //遍历文本串的所有字符
u = ch[u][c-'a'];
for(int t=u; t && en[t]!=-1; t=fail[t]){ //不断假设当前失配,遍历到所有匹配情况
//en[t]==-1代表这条线已经遍历过,遍历过那不再重复计算贡献了
res += en[t];
en[t] = -1;
}
}
return res;
}
int main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int n; string s;
cin>>n; for(int i=1; i<=n; i++) {cin>>s; build(s);}
get_fail();
cin>>s; cout<<solve(s)<<'\n';
}
update 4月6日
【模板】AC 自动机(加强版) - 洛谷 (做法和上面一样,水题)
#include <bits/stdc++.h>
using namespace std;
#define FOR(i, a, b) for (int i = (a); i <= (b); i++)
#define ROF(i, a, b) for (int i = (a); i >= (b); i--)
const int N=11000;
int n;
int ch[N][26],fail[N],tot,en[N],num[N];
string ss[180]; //所有模式串
map<int,int> mp; //{end_id, string_id}
inline void build(const string&s){ //普通的trie树build函数
int len=s.size(), x, u=0;
for(int i=0; i<len; i++){
x=s[i]-'a';
if(!ch[u][x]) ch[u][x] = ++tot; //如果没有这个点,就建立
u=ch[u][x];
}
en[u]++; //记录出现次数
}
inline void get_fail(){ //ac自动机的精髓,求解fail数组
queue<int> q;
for(int i=0; i<26; i++) //特别处理第一层
if(ch[0][i]) q.push(ch[0][i]); //第一层结点加入队列
//注意:所有第一层结点的fail值都没有处理过,也就是默认他们的fail值都是0,回到root点
while(q.size()){ //bfs求fail指针
int u=q.front(); q.pop(); //父结点
for(int i=0; i<26; i++){
if(ch[u][i]){ //有这个子结点
fail[ch[u][i]] = ch[fail[u]][i]; //核心步骤,fail = fa->fail->child
q.push(ch[u][i]);
} else {ch[u][i] = ch[fail[u]][i];} //特殊处理,建立虚拟子结点,实际上相当于失配
}
}
}
inline void init(){
mp.clear();
for(int i=1; i<=n; i++) num[i]=0; //出现次数重置
ROF(i,tot,0){
FOR(j,0,25) ch[i][j]=0;
fail[i]=0;
en[i]=0;
}
tot=0;
}
inline void solve(){
init();
string s;
FOR(i,1,n) cin>>ss[i], build(ss[i]), mp[tot]=i; //记录映射
get_fail();
cin>>s;
int u=0;
for(char c:s){
u=ch[u][c-'a'];
for(int t=u; t; t=fail[t]){
if(en[t]) num[mp[t]] += en[t]; //计算答案
}
}
int ans=0;
FOR(i,1,n) ans=max(ans,num[i]);
cout<<ans<<'\n';
FOR(i,1,n) if(num[i]==ans) cout<<ss[i]<<'\n';
}
int main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
while(cin>>n && n) solve();
}
update 4月7日
【模板】AC 自动机(二次加强版) - 洛谷 (学到新东西,在需要遍历多次的情况下,不能用en=-1标记,暴力跳fail的时间复杂度是O(模式串长度之和*文本串长度,需要优化)
这里优化方法是遍历到每个点之后打上标记,最后一起处理,相当于差分的思路,但由于是个图,最后汇总的时候要按拓扑序,所以要拓扑排序。
#include <bits/stdc++.h>
using namespace std;
#define FOR(i, a, b) for (int i = (a); i <= (b); i++)
#define ROF(i, a, b) for (int i = (a); i >= (b); i--)
const int N=2e5+50;
int n;
int ch[N][26],fail[N],tot,en[N],cnt[N];
int id[N], ans[N], in[N]; //in表示入度
inline void build(int cur, const string&s){ //普通的trie树build函数
int len=s.size(), x, u=0;
for(int i=0; i<len; i++){
x=s[i]-'a';
if(!ch[u][x]) ch[u][x] = ++tot; //如果没有这个点,就建立
u=ch[u][x];
}
en[u]=1; //记录出现次数
id[cur] = u; //记录末尾标号
}
inline void get_fail(){ //ac自动机的精髓,求解fail数组
queue<int> q;
for(int i=0; i<26; i++) //特别处理第一层
if(ch[0][i]) q.push(ch[0][i]); //第一层结点加入队列
//注意:所有第一层结点的fail值都没有处理过,也就是默认他们的fail值都是0,回到root点
while(q.size()){ //bfs求fail指针
int u=q.front(); q.pop(); //父结点
for(int i=0; i<26; i++){
if(ch[u][i]){ //有这个子结点
fail[ch[u][i]] = ch[fail[u]][i]; //核心步骤,fail = fa->fail->child
in[ch[fail[u]][i]]++; //入度+1(这里用到优化自动机,要加上这步,用于拓扑排序)
q.push(ch[u][i]);
} else {ch[u][i] = ch[fail[u]][i];} //特殊处理,建立虚拟子结点,实际上相当于失配
}
}
}
inline void solve(){
string s;
FOR(i,1,n) cin>>s, build(i,s); //记录映射
get_fail();
cin>>s;
int u=0;
for(char c:s){
u=ch[u][c-'a'];
cnt[u]++; //访问次数+1(优化自动机不用跳fail,跳fail过程统一留到最后)
}
//下面开始拓扑排序处理标记
queue<int> q;
FOR(i,1,tot) if(in[i]==0) q.push(i);
while(q.size()){
int cur=q.front(); q.pop();
ans[cur]=cnt[cur];
int nxt = fail[cur];
in[nxt]--;
cnt[nxt] += cnt[cur];
if(in[nxt]==0) q.push(nxt);
}
FOR(i,1,n) cout<<ans[id[i]]<<'\n';
}
int main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin>>n; solve();
}