1. 程式人生 > 遊戲 >國外玩家爺爺打通《質量效應:傳奇版》 30年來最好的樂子

國外玩家爺爺打通《質量效應:傳奇版》 30年來最好的樂子

模板題傳送門

百度百科傳送門

AC自動機分為三個部分:

  1. 構造trie樹

  2. 構造fail指標

  3. 模式匹配

1.構造trie樹

在trie樹上,從根節點開始,到某一葉子節點的路徑即為一個字串。

每個節點有n個出度,代表字串每一位有n種情況。

如,對於二進位制數,n=2 ;對於小寫字母字串,n=26 。

因此,構建trie樹,只需從根節點開始,沿著字串向下移動,並不斷補充新節點即可。

最後在葉子節點上進行相應的處理即可(具體處理方法根據題意)。

以二進位制為例,trie樹處理結果如圖:

程式碼:

void build_trie(char x[]){
	int len=strlen(x),p=0;
	for(int i=0;i<len;++i){
		int t=x[i]-'a';
		if(!pre[p][t])pre[p][t]=++tot;
		p=pre[p][t];
	}
	++val[p];
}

2.構造fail指標

KMP只支援 單串與單串 之間的比較,而AC自動機通過新增fail指標,記錄最大相同字首字尾,滿足 單串與多串 的比較。

對於某一節點,其子節點的fail為其fail的相同子節點。

特別的,根節點的子節點的fail為根節點。

建立了fail指標後,當某個子節點不存在時,就可以順著fail往上跳,保證連貫性。

以二進位制為例,fail指標處理結果如圖:

程式碼:

void build_AC(){
	for(int i=0;i<26;++i)
		if(pre[0][i])q.push(pre[0][i]);
	while(!q.empty()){
		int x=q.front();q.pop();
		for(int i=0;i<26;++i){
			if(pre[x][i]){
				q.push(pre[x][i]);
				fail[pre[x][i]]=pre[fail[x]][i];
			}
			else pre[x][i]=pre[fail[x]][i];
		}
	}
}

————————————————————

3.模式匹配

準備工作做完後,接下來開始匹配。

同樣是順著字串往下走,與第一步不同的是,這次不能建立新節點,同時要關注fail。

因為fail記錄的是最大相同字首字尾,則fail的fail記錄的是所有的相同字首字尾。

因此,對於一個節點,只要一直往上fail,就可以訪問到所有可能存在的答案。

程式碼:

int query_AC(char x[]){
	int len=strlen(x),p=0,res=0;
	for(int i=0;i<len;++i){
		int t=x[i]-'a';
		p=pre[p][t];
		for(int j=p;j && ~val[j];j=fail[j]){
			res+=val[j];val[j]=-1;
		}
	}
	return res;
}

完成以上三個步驟,AC自動機就建好了。

完整程式碼:

#include<bits/stdc++.h>
using namespace std;
const int M=1e6+5;
int n;
char s[M];
queue<int> q;
struct AC_AUTO{
	int pre[M][30],fail[M],val[M],tot;
	void build_trie(char x[]){
		int len=strlen(x),p=0;
		for(int i=0;i<len;++i){
			int t=x[i]-'a';
			if(!pre[p][t])pre[p][t]=++tot;
			p=pre[p][t];
		}
		++val[p];
	}
	void build_AC(){
		for(int i=0;i<26;++i)
			if(pre[0][i])q.push(pre[0][i]);
		while(!q.empty()){
			int x=q.front();q.pop();
			for(int i=0;i<26;++i){
				if(pre[x][i]){
					q.push(pre[x][i]);
					fail[pre[x][i]]=pre[fail[x]][i];
				}
				else pre[x][i]=pre[fail[x]][i];
			}
		}
	}
	int query_AC(char x[]){
		int len=strlen(x),p=0,res=0;
		for(int i=0;i<len;++i){
			int t=x[i]-'a';
			p=pre[p][t];
			for(int j=p;j && ~val[j];j=fail[j]){
				res+=val[j];val[j]=-1;
			}
		}
		return res;
	}
}AC;
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;++i){
		scanf("%s",s);
		AC.build_trie(s);
	}
	AC.build_AC();
	scanf("%s",s);
	printf("%d\n",AC.query_AC(s));
	return 0;
}
\[\mathcal{By}\quad\mathcal{Most}\ \mathcal{Handsome} \]