1. 程式人生 > >學習筆記第二十七節:AC自動機

學習筆記第二十七節:AC自動機

正題

      聽說NOIP要考,所以臨時補了一下,多了一種思考方式。

      AC自動機是基於KMP和字典樹的,要想透徹瞭解AC自動機,最好先學KMP和字典樹。

      那麼,AC自動機是什麼樣子的呢?又是用來解決什麼問題的呢?

      【模板】AC自動機(簡單版),可以以這題為例。

      好像KMP是可以做的,試一下,發現時間複雜度會爆炸,因為文字串要遍歷N次。

      通過磨題解,我們學習新演算法。

      AC自動機。

      首先我們把n個模式串插入一個字典樹中,接著,神奇的事情發生了。

      我們對於字典樹中的每一個節點,我們找其對應的fail指標。

      fail指標指的是什麼?從根到該節點的路徑很明顯構成一個字串。

      那麼fail指標指向的就是這個字串,在字典樹中出現的最長的字尾。如圖。

      是不是很醜,因為fail指標很好理解。

      接著怎麼解決這一題呢?

      先把AC自動機建出來,給每個字串的結尾所對應的節點權值+1.

      然後拿文字串上去跳,讓它從根節點一直往下走(按照字元),如果一個節點沒有對應的字元,就不斷跳fail,直到跳到根。

      如果當前節點是一個模式串的結尾,那麼ans+這個節點的權值,並且給這個節點打一個標記,表示記錄過了。(不計重複)

      同時要加上fail指標的答案,因為滿足這個點一定滿足fail,fail指標是我的字尾啊。

#include<queue>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
using namespace std;

int n;
struct node{
	int t,son[26],fail;
	bool tf;
}s[1000010];
char ch[1000010];
int tot=0;
queue<int> f;

void insert(){
	int l=strlen(ch+1);
	int x=0;
	for(int i=1;i<=l;i++){
		int temp=ch[i]-'a';
		if(s[x].son[temp]==-1){
			s[x].son[temp]=++tot;
			s[tot].t=0;memset(s[tot].son,-1,sizeof(s[tot].son));
		}
		x=s[x].son[temp];
	}
	s[x].t++;
}

void get_fail(){
	f.push(0);	
	while(!f.empty()){
		int x=f.front();f.pop();
		for(int i=0;i<26;i++){
			int y=s[x].son[i];
			if(y==-1) continue;
			int now=s[x].fail;
			while(now!=0 && s[now].son[i]==-1) now=s[now].fail;
			if(x!=0 && s[now].son[i]!=-1) s[y].fail=s[now].son[i];
			f.push(y);
		}
	}
}

int get_ans(){
	int ans=0;
	int x=0,l=strlen(ch+1);
	for(int i=1;i<=l;i++){
		int temp=ch[i]-'a';
		while(x!=0 && s[x].son[temp]==-1) x=s[x].fail;
		if(s[x].son[temp]!=-1) x=s[x].son[temp];
		int now=x;
		while(now!=0 && !s[now].tf) ans+=s[now].t,s[now].tf=true,now=s[now].fail;
	}
	return ans;
}

int main(){
	scanf("%d",&n);
	memset(s[0].son,-1,sizeof(s[0].son));
	for(int i=1;i<=n;i++) {
		scanf("%s",ch+1);
		insert();
	}
	scanf("%s",ch+1);
	get_fail();
	printf("%d",get_ans());
}