AC自动机复习
AC 自动机
应用:
- 多模式串匹配
构建
- 将所有模式串构成一个 Trie 树
- 树上的每个节点代表某一个(或多个)模式串的前缀
- 每个节点同时有一个失配指针\(fail\),指向当前节点所代表的的串可以匹配到的最长前缀,这个前缀还必须是这个串的后缀。类似于\(KMP\)的\(next\)数组,但\(KMP\)的\(next\)数组是对于一个串而言,在 AC 自动机里一个节点的\(fail\)指针不一定指向自己的前缀,可以是其他模式串的前缀。
- 寻找\(fail\)指针,可以假设比这个极点高度低的极点的\(fail\)都寻找完了,所以我们是通过\(BFS\)的方式去寻找\(fail\)指针
- 根据题目的不同,每个节点额外记录的信息也不同,比如每个节点可以表示的模式串个数\(cnt\),每个节点表示的模式串的编号\(id\)等等
模板
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;
namespace AC
{
int tr[N][26],tot;
int fail[N],cnt[N]; //cnt[i]表示第i个节点可以表示的模式串个数
void insert(char *s){ //构建Trie树
int u=0;
for(int i=0;s[i];i++){
int x=s[i]-'a';
if(!tr[u][x]) tr[u][x]=++tot;
u=tr[u][x];
}
cnt[u]++;
}
void build(){
queue<int>q;
for(int i=0;i<26;i++) //先把所有0号节点可以到达的点入队
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++){
int &x=tr[u][i];
if(x){
fail[x]=tr[fail[u]][i];
q.push(x);
}else{
x=tr[fail[u]][i];
}
}
}
}
}
char s[N],t[N];
int main()
{
//ios::sync_with_stdio(false);
//cin.tie(nullptr);
int n;
scanf("%d",&n);
for(int i=0;i<n;i++) {
scanf("%s",s);
AC::insert(s);
}
AC::build();
}
应用
1.多模式串匹配 1
Problem
多模式串匹配:给定一个文本串\(t\)和多个模式串\(s\),问有多少个模式串在文本串中出现过
Solve
每个节点记录表示的模式串个数,然后在 Trie 树上沿着文本串走,当走到一个节点假设这个节点是从\(x\)转移过来的,那么沿着这个节点跳\(fail\),因为若存在其他模式串\(s\)并且是以\(x\)结尾的,那么在当前文本串走过的路径形成的字符串\(t^{'}\)中,\(s\)若在\(t^{'}\)中出现,则一定是\(t^{'}\)的一个后缀,然后我们对每个跳过的节点标记,这样对于相同的\(x\)就不会重复了。
Code
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;
namespace AC
{
int tr[N][26],tot;
int fail[N],cnt[N]; //cnt[i]表示第i个节点可以表示的模式串个数
void insert(char *s){ //构建Trie树
int u=0;
for(int i=0;s[i];i++){
int x=s[i]-'a';
if(!tr[u][x]) tr[u][x]=++tot;
u=tr[u][x];
}
cnt[u]++;
}
void build(){
queue<int>q;
for(int i=0;i<26;i++) //先把所有0号节点可以到达的点入队
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++){
int &x=tr[u][i];
if(x){
fail[x]=tr[fail[u]][i];
q.push(x);
}else{
x=tr[fail[u]][i];
}
}
}
}
int query(char *t){
int u=0,res=0;
for(int i=0;t[i];i++){
u=tr[u][t[i]-'a'];
for(int j=u;j&&cnt[j]!=-1;j=fail[j]){
res+=cnt[j],cnt[j]=-1;
}
}
return res;
}
}
char s[N],t[N];
int main()
{
int n;
scanf("%d",&n);
for(int i=0;i<n;i++) {
scanf("%s",s);
AC::insert(s);
}
AC::build();
scanf("%s",t);
printf("%d\n",AC::query(t));
}
2.多模式串匹配 2
Problem
给定多个模式串\(s\)和一个文本串\(t\),问哪些模式串在文本串中出现的次数最多,并输出这些模式串
Solve
由于不会有两个模式串相同,所以可以给每个节点记录一个可以表示的模式串的编号。然后沿着\(t\)在 Trie 上走,每次到达一个节点就跳\(fail\),并且记录每个节点是通过跳\(fail\)到达的次数,最后查询哪些可以表示字符串的节点可以被跳到的最多次数即可。
Code
#include <bits/stdc++.h>
using namespace std;
const int N=155;
namespace AC
{
const int NN=N*80;
int tr[NN][26],tot;
int fail[NN],cnt[NN],id[NN]; //cnt[i]表示第i个节点可以表示的模式串个数
int val[NN];
void init(){
tot=0;
memset(tr,0,sizeof tr);
memset(fail,0,sizeof fail);
memset(cnt,0,sizeof cnt);
memset(id,0,sizeof id);
memset(val,0,sizeof val);
}
void insert(char *s,int idx){ //构建Trie树
int u=0;
for(int i=0;s[i];i++){
int x=s[i]-'a';
if(!tr[u][x]) tr[u][x]=++tot;
u=tr[u][x];
}
id[u]=idx;
cnt[u]++;
}
void build(){
queue<int>q;
for(int i=0;i<26;i++) //先把所有0号节点可以到达的点入队
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++){
int &x=tr[u][i];
if(x){
fail[x]=tr[fail[u]][i];
q.push(x);
}else{
x=tr[fail[u]][i];
}
}
}
}
int query(char *t){
int u=0,res=0;
for(int i=0;t[i];i++){
u=tr[u][t[i]-'a'];
for(int j=u;j;j=fail[j]) val[j]++;
}
for(int i=0;i<=tot;i++)
if(id[i]) res=max(res,val[i]),cnt[id[i]]=val[i];
return res;
}
}
char s[151][76];
char t[1000005];
int main()
{
int n;
while(scanf("%d",&n),n){
AC::init();
for(int i=1;i<=n;i++){
scanf("%s",s[i]);
AC::insert(s[i],i);
}
AC::build();
scanf("%s",t);
int res=AC::query(t);
printf("%d\n",res);
for(int i=1;i<=n;i++)
if(AC::cnt[i]==res) printf("%s\n",s[i]);
}
}