AC自动机:
解决多模式串匹配问题
算法流程:
基本流程:
1.建立trie树
2.构造fail指针(指向最长后缀节点,用bfs实现)
3.查询
P3808 AC自动机(简单版)
题意:
给定n个模式串和1个文本串,求有多少个模式串在文本串里出现过。
思路:
val(x)数组记录以x结尾的模式串个数,查询的时候累加即可
为了防止fail指针跳回去的时候重复累加,当查询到某个节点之后,把这个节点的val清空,保证只累加一次。
code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=1e6+5;
struct AC{
int a[maxm][26],fail[maxm],val[maxm],tot=0;
queue<int>q;
void init(){//清空
while(!q.empty())q.pop();
for(int i=0;i<=tot;i++){
for(int j=0;j<26;j++){
a[i][j]=0;
}
val[i]=fail[i]=0;
}
tot=0;
}
void add(char *s){//建立trie树
int len=strlen(s);
int node=0;
for(int i=0;i<len;i++){
int v=s[i]-'a';
if(!a[node][v])a[node][v]=++tot;
node=a[node][v];
}
val[node]++;//标记结尾
}
void build(){//构造fail
for(int i=0;i<26;i++){
if(a[0][i]){
q.push(a[0][i]);
}
}
while(!q.empty()){
int x=q.front();
q.pop();
for(int i=0;i<26;i++){
if(a[x][i]){//如果有该字符子节点
fail[a[x][i]]=a[fail[x]][i];//(子节点的fail)指向(父节点fail的该字符节点)
q.push(a[x][i]);
}else{//如果没有该字符子节点
a[x][i]=a[fail[x]][i];//(该字符子节点)指向(父节点fail的该字符节点)
}
}
}
}
int ask(char *s){
int len=strlen(s);
int ans=0;
int node=0;
for(int i=0;i<len;i++){
int v=s[i]-'a';
node=a[node][v];
for(int t=node;t&&val[t];t=fail[t]){
ans+=val[t];
val[t]=0;//匹配过的删掉
}
}
return ans;
}
}ac;
char s[maxm];
signed main(){
ac.init();
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%s",s);
ac.add(s);
}
ac.build();
scanf("%s",s);
int ans=ac.ask(s);
printf("%d\n",ans);
return 0;
}
P3796 AC自动机(加强版)
题意:
给定n个模式串和一个文本串。
要求输出模式串最多出现的次数,以及次数最多的是哪些模式串。
思路:
val(x)数组记录以x结尾的模式串id,
ans(x)记录第x个串出现的次数
查询的时候记录每个id的串出现的次数,查询完之后遍历ans数组取max即可。
code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=1e6+5;
char s[155][maxm];
char t[maxm];
int ans[maxm];
struct AC{
int a[maxm][26],fail[maxm],val[maxm],tot=0;
queue<int>q;
void init(){//清空
while(!q.empty())q.pop();
for(int i=0;i<=tot;i++){
for(int j=0;j<26;j++){
a[i][j]=0;
}
val[i]=fail[i]=0;
}
tot=0;
}
void add(char *s,int idx){//建立trie树
int len=strlen(s);
int node=0;
for(int i=0;i<len;i++){
int v=s[i]-'a';
if(!a[node][v])a[node][v]=++tot;
node=a[node][v];
}
val[node]=idx;
}
void build(){//构造fail
for(int i=0;i<26;i++){
if(a[0][i]){
q.push(a[0][i]);
}
}
while(!q.empty()){
int x=q.front();
q.pop();
for(int i=0;i<26;i++){
if(a[x][i]){//如果有该字符子节点
fail[a[x][i]]=a[fail[x]][i];//(子节点的fail)指向(父节点fail的该字符节点)
q.push(a[x][i]);
}else{//如果没有该字符子节点
a[x][i]=a[fail[x]][i];//(该字符子节点)指向(父节点fail的该字符节点)
}
}
}
}
void ask(char *s){
int len=strlen(s);
int node=0;
for(int i=0;i<len;i++){
int v=s[i]-'a';
node=a[node][v];
for(int t=node;t;t=fail[t]){
ans[val[t]]++;
}
}
}
}ac;
signed main(){
int n;
while(scanf("%d",&n)!=EOF&&n){
ac.init();
for(int i=1;i<=n;i++)ans[i]=0;
for(int i=1;i<=n;i++){
scanf("%s",s[i]);
ac.add(s[i],i);
}
ac.build();
scanf("%s",t);
ac.ask(t);
int ma=1;
for(int i=1;i<=n;i++){
if(ans[i]>ans[ma]){
ma=i;
}
}
printf("%d\n",ans[ma]);
for(int i=1;i<=n;i++){
if(ans[i]==ans[ma]){
printf("%s\n",s[i]);
}
}
}
return 0;
}
P5357 AC自动机(二次加强版)
题意:
给n个模式串和一个文本串,要求输出每个模式串在文本串中出现的次数
思路:
1.和上面一题“P3796 AC自动机(加强版)”似乎差不多,稍微改改就行了。提交发现只有40分。
2.原因是会有重复的串,怎么办呢?
一种方法是在每个节点建立一个链表,这样就能存下多个点的id了。
另一种方法是将每个串的id映射到某个数上idd(id)=x,相同串的idd相同,然后节点存放idd(id)即x,最后ans(idd(id))就是答案。
提交发现只有72分。
3.原因是这题的数据比较强,我们知道每次跳fail指针,层数会变小,但是最少变小一层,如果每次都只减少一层,非常慢。
需要优化一下跳fail的过程:这篇文章里面的拓扑排序优化。
优化完就能ac了。
code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=2e6+5;
char s[maxm];
int ans[maxm];
int idd[maxm];//映射
int in[maxm];//入度
struct AC{
int a[maxm][26],fail[maxm],val[maxm],tot=0;
int res[maxm];
queue<int>q;
void init(){//清空
while(!q.empty())q.pop();
for(int i=0;i<=tot;i++){
for(int j=0;j<26;j++){
a[i][j]=0;
}
val[i]=fail[i]=0;
}
tot=0;
}
void add(char *s,int idx){//建立trie树
int len=strlen(s);
int node=0;
for(int i=0;i<len;i++){
int v=s[i]-'a';
if(!a[node][v])a[node][v]=++tot;
node=a[node][v];
}
if(!val[node])val[node]=idx;
idd[idx]=val[node];
}
void build(){//构造fail
for(int i=0;i<26;i++){
if(a[0][i]){
q.push(a[0][i]);
}
}
while(!q.empty()){
int x=q.front();
q.pop();
for(int i=0;i<26;i++){
if(a[x][i]){//如果有该字符子节点
fail[a[x][i]]=a[fail[x]][i];//(子节点的fail)指向(父节点fail的该字符节点)
in[fail[a[x][i]]]++;//入度增加
q.push(a[x][i]);
}else{//如果没有该字符子节点
a[x][i]=a[fail[x]][i];//(该字符子节点)指向(父节点fail的该字符节点)
}
}
}
}
void ask(char *s){
int len=strlen(s);
int node=0;
for(int i=0;i<len;i++){
int v=s[i]-'a';
node=a[node][v];
res[node]++;
}
}
void topo(){//拓扑
for(int i=1;i<=tot;i++){
if(!in[i])q.push(i);
}
while(!q.empty()){
int x=q.front();
q.pop();
ans[val[x]]+=res[x];
int v=fail[x];
res[v]+=res[x];
in[v]--;
if(!in[v])q.push(v);
}
}
}ac;
signed main(){
int n;
scanf("%d",&n);
ac.init();
for(int i=1;i<=n;i++){
scanf("%s",s);
ac.add(s,i);
}
ac.build();
scanf("%s",s);
ac.ask(s);
ac.topo();
for(int i=1;i<=n;i++){
printf("%d\n",ans[idd[i]]);
}
return 0;
}