1. 程式人生 > 實用技巧 >AC自動機---好東西

AC自動機---好東西

一.前言

想學AC自動機,請先學會KMPTrie樹

二.正文

AC自動機到底是什麼呢?\(Trie+KMP\)

我們的AC自動機和KMP有什麼區別呢?

AC自動機可以同時匹配很多個模式串,而KMP只可以匹配一個模式串。

那麼我們來看一道例題:傳送門

我們可以想到對於每一個字串進行一次KMP,然鵝會TLE。

AC自動機就是一個演算法,它可以同時進行多次KMP。

我們舉一個例子:

我們可以驚奇的發現:

這裡\(she\)的字尾\(he\),和\(he\)是一樣的,這時候我們想到了什麼?

我們可以建立一個\(fail_{cur}\)的陣列,在\(she\)後的\(e\)建立一個指向\(he\)

中的\(e\)的指標。像這樣

我們就可以很方便的進行匹配了。

現在我們的關鍵就在於如何求出\(fail\)

假設我們有一點\(trie[cur][i]\),它的父親\(cur\),若\(cur\)\(fail\)已經求出,那麼只會有兩種情況

  1. 我們的\(trie[cur][i]\)不為空,那麼就直接把\(fail[trie[cur][i]]=fail[cur][i]\),意思就是在自己的\(fail_{cur}\)下尋找一個\(i\)代表的陣列。
  2. \(trie[cur][i]\)為空,直接把這個點\(trie[cur][i]=trie[fail[cur]][i]\)

這樣就好了。

我們查詢的時候就一個字元一個字元的找,每一次針對一個字元,不停地根據\(fail\)

往上跳,直到跳不動為止。

在這個過程中,我們要注意統計一次答案,就要把\(Trie\)中的計數器\(cnt\)清空為\(-1\),代表跳到這裡就跳不了了。

我們就可以輕鬆的打出程式碼

Code:

#include<iostream>
#include<queue>
using namespace std;
const int N = 1000005;
int trie[N][28], tot, num[N], fail[N];
queue<int> q;

void insert(string s) 
{
	int cur = 0;
	for(int i = 0; i < s.size(); i++) 
	{
		if(trie[cur][s[i]-'a'] == 0)
		{
			tot++; 
			trie[cur][s[i]-'a'] = tot;
		}
		cur = trie[cur][s[i]-'a'];
	}
	num[cur]++; //統計以cur結尾的單詞出現的次數 
	return ;
}

void get_fail() //bfs構建fail陣列 
{
	for(int i = 0; i < 26; i++) //根結點下面直接連的第一層結點,fail直接指向根結點0 
		if(trie[0][i]) 
			q.push(trie[0][i]);
			
	while(q.empty() == false) //佇列中維護能夠拓展fail值的結點 
	{
		int cur = q.front();
		q.pop();
		for(int i = 0; i < 26; i++) 
		{
			if(trie[cur][i])
			{
				//失配時,以trie[u][i]結尾的字尾儘量在trie中找一個與之相同的字首(類似KMP) 
				fail[trie[cur][i]] = trie[fail[cur]][i];
				q.push(trie[cur][i]);
			}
			else //節點不存在,往上連,最多回到根結點0, 注意是trie不是fail陣列 
				trie[cur][i] = trie[fail[cur]][i];
		}
	}
	return ;
}

int query(string t) //詢問,t是文字串 
{
	int cur = 0, res = 0; //cur表示trie中的結點 
	for(int i = 0; i < t.size(); i++) 
	{
		cur = trie[cur][t[i] - 'a']; //獲取t[i]所對應的結點 
		for(int j = cur; j && num[j] != -1; j = fail[j]) 
		{
	  		res += num[j];
			num[j] = -1; //標記為統計過 
		}
	}
	return res;
}

int main() 
{
	int n;
	string s;
	cin >> n;
	for(int i = 1; i <= n; i++) 
	{
		cin >> s;
		insert(s);
	}
	cin >> s; //文字串 
	get_fail();
	cout << query(s);
	return 0;
}