AC自動機總結
阿新 • • 發佈:2021-07-30
AC自動機:用來解決多模式串匹配問題。
模式串:要尋找的那個字串。
AC自動機的本質可以看作KMP+Trie樹。KMP構造next陣列,目的是為了在每次失配過後,都找到當前仍能匹配的最長長度,針對一個模式串。AC自動機則是對於多個模式串來說,每次失配都找到所有模式串中,還能匹配的最長的部分。在實現方面,通過fail失配指標來實現。
fail指標指向的是當前點代表的字首子串在所有模式串中的最長字尾。
構造方法:
1.對所有模式串建立字典樹。
2.BFS求出fail指標。比較關鍵的部分是fail[v] = tr[fail[u]][i];這一行程式碼的意思是,設當前結點的字元為i,則當前結點的fail指標指向其父親的fail指標的i兒子。
另有一個跳邊的優化:若i兒子不存在,tr[u][i] = tr[fail[u]][i];,則直接將該節點的i兒子設為fail指標的i兒子。
P3808 【模板】AC自動機(簡單版)
給定\(n\)個模式串\(s_{i}\)和一個文字串\(t\),求有多少個不同的模式串在文字串裡出現過。
#include<bits/stdc++.h> using namespace std; const int MAXN = 1000005; inline int read(){ int k = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){if(ch == '-') f = -1; ch = getchar();} while(ch >= '0' && ch <= '9'){k = k*10 + ch - '0'; ch = getchar();} return k * f; } int tr[MAXN][26], val[MAXN], fail[MAXN], cnt; char s[MAXN]; void insert(char *str){ int u = 0; for(int i = 0; str[i] != '\0'; i++){ if(tr[u][str[i] - 'a'] == 0){ tr[u][str[i] - 'a'] = ++cnt; } u = tr[u][str[i] - 'a']; } val[u]++; } void bfs(int u){ queue <int> q; for(int i = 0; i < 26; i++){ if(tr[0][i]){ fail[tr[0][i]] = 0; q.push(tr[0][i]); } } while(!q.empty()){ u = q.front(); q.pop(); // printf("u = %d\n", u); for(int i = 0; i < 26; i++){ int v = tr[u][i]; if(v){ fail[v] = tr[fail[u]][i]; q.push(v); // printf("v = %d, fail = %d\n", v, fail[v]); } else{ //youhua tr[u][i] = tr[fail[u]][i]; } } } } int query(char *str){ int u = 0, ret = 0; for(int i = 0; str[i] != '\0'; i++){ u = tr[u][str[i] - 'a']; // ret += val[u]; //跳邊尋找所有滿足條件的模式串 for(int t = u; t != 0 && val[t] != -1; t = fail[t]){ ret += val[t]; val[t] = -1; } } return ret; } int main(){ int n = read(); for(int i = 1; i <= n; i++){ scanf("%s", s); insert(s); } // printf("cnt = %d\n", cnt); bfs(0); // for(int i = 1; i <= cnt; i++){ // printf("fail[%d] = %d\n", i, fail[i]); // } scanf("%s", s); int ans = query(s); printf("%d", ans); return 0; }
P3796 【模板】AC自動機(加強版)
同簡單版,唯一不同的地方在於統計的方法不一樣。
#include<bits/stdc++.h> using namespace std; const int MAXN = 1000005; inline int read(){ int k = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){if(ch == '-') f = -1; ch = getchar();} while(ch >= '0' && ch <= '9'){k = k*10 + ch - '0'; ch = getchar();} return k * f; } int tr[MAXN][26], val[MAXN], fail[MAXN], cnt; int tim[1000]; char s[200][100]; char ch[MAXN]; void insert(char *str, int num){ int u = 0; for(int i = 0; str[i] != '\0'; i++){ if(tr[u][str[i] - 'a'] == 0){ // printf("cnt = %d\n", cnt); // printf("%c, str[i]", str[i]); tr[u][str[i] - 'a'] = ++cnt; } u = tr[u][str[i] - 'a']; } val[u] = num; } void bfs(int u){ queue <int> q; for(int i = 0; i < 26; i++){ if(tr[0][i]){ fail[tr[0][i]] = 0; q.push(tr[0][i]); } } while(!q.empty()){ u = q.front(); q.pop(); for(int i = 0; i < 26; i++){ int v = tr[u][i]; if(v){ fail[v] = tr[fail[u]][i]; q.push(v); } else{ //youhua tr[u][i] = tr[fail[u]][i]; } } } } void query(char *str){ int u = 0, ret = 0; for(int i = 0; str[i] != '\0'; i++){ u = tr[u][str[i] - 'a']; for(int t = u; t != 0; t = fail[t]){ if(val[t]){ tim[val[t]]++; } } } return; } int main(){ while(1){ int n = read(); if(n == 0) break; cnt = 0; memset(tim, 0, sizeof(tim)); memset(tr, 0, sizeof(tr)); memset(val, 0, sizeof(val)); memset(fail, 0, sizeof(fail)); for(int i = 1; i <= n; i++){ scanf("%s", s[i]); // printf("%s\n", s[i]); insert(s[i], i); } bfs(0); scanf("%s", ch); // printf("ch = %s\n", ch); query(ch); int t = 1; for(int i = 1; i <= n; i++){ if(tim[t] < tim[i]){ t = i; } } printf("%d\n", tim[t]); for(int i = 1; i <= n; i++){ if(tim[t] == tim[i]){ printf("%s\n", s[i]); } } } return 0; }
P5357 【模板】AC自動機(二次加強版)
不同點在於可能會出現相同的模式串。這樣的話暴力跳fail匹配複雜度最高可以到\(n^2\)可能會被卡。
解決方法:可以發現每個結點的fail邊構成了一顆樹。由於每次跳fail邊都會跳到不能再跳為止,因此每次到一個點,都會對該點到根的路徑上每一個點有貢獻。因此採用拓撲排序,從葉結點開始累加答案,這樣避免了暴力跳邊。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2000005;
inline int read(){
int k = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9'){k = k*10 + ch - '0'; ch = getchar();}
return k * f;
}
int tr[MAXN][26], val[MAXN], fail[MAXN], cnt;
int tim[MAXN], pos[MAXN];
//int flag[MAXN];
int du[MAXN];
char s[MAXN];
int vis[MAXN];
queue <int> q;
void insert(char *str, int num){
int u = 0;
for(int i = 0; str[i] != '\0'; i++){
if(tr[u][str[i] - 'a'] == 0){
tr[u][str[i] - 'a'] = ++cnt;
}
u = tr[u][str[i] - 'a'];
}
pos[num] = u;
if(!val[u]){
val[u] = num;
}
}
void bfs(int u){
while(!q.empty()) q.pop();
for(int i = 0; i < 26; i++){
if(tr[0][i]){
fail[tr[0][i]] = 0;
q.push(tr[0][i]);
}
}
while(!q.empty()){
u = q.front(); q.pop();
for(int i = 0; i < 26; i++){
int v = tr[u][i];
if(v){
fail[v] = tr[fail[u]][i];
du[fail[v]]++;//
q.push(v);
}
else{ //youhua
tr[u][i] = tr[fail[u]][i];
}
}
}
}
void query(char *str){
int u = 0;
for(int i = 0; str[i] != '\0'; i++){
u = tr[u][str[i] - 'a'];
tim[u]++;
}
return;
}
int main(){
// freopen("out.txt", "r", stdin);
// freopen("out1.txt", "w", stdout);
int n = read();
for(int i = 1; i <= n; i++){
scanf("%s", s);
insert(s, i);
}
bfs(0);
scanf("%s", s);
query(s);
// for(int i = 1; i <= n; i++){
// int u = pos[i];
// if(flag[u]) continue;
// for(int t = fail[u]; t != 0; t = fail[t]){
// if(val[t]){
// du[t]++;
// break;
// }
// }
// flag[u] = 1;
// }
// for(int i = 1; i <= cnt; i++){
// printf("du = %d\n", du[i]);
// }
while(!q.empty()) q.pop();
for(int i = 1; i <= cnt; i++){
if(du[i] == 0){
q.push(i);
}
}
while(!q.empty()){
int u = q.front(); q.pop();
int v = fail[u];
tim[v] += tim[u];
// vis[val[u]] = tim[u];
du[v]--;
if(du[v] == 0){
q.push(v);
}
}
for(int i = 1; i <= n; i++){
printf("%d\n", tim[pos[i]]);
}
return 0;
}