1. 程式人生 > 其它 >AC自動機總結

AC自動機總結

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