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;
}