AC自動機[模板+圖講解]
目錄~
f a i l fail fail指標的匹配過程
先把 n n n個模式串建立字典樹
我們拿文字串 a b c d e abcde abcde進去匹配
一開始進入最左邊的分支 a b c d abcd abcd,成功匹配到一個!!!然後就沒然後了,然後開始從 b b b暴力匹配…
不是的,這裡我們也應該像 K M P KMP KMP那樣利用已經匹配的性質進行跳躍
我們定義 f a i l [ i ] fail[i] fail[i]為與節點 i i i失配後跳到的下一個節點
比如,匹配完 a b c d abcd abcd後,在 e e e位置失配…因為不存在這個節點了!!!
那麼我們可以直接跳到 b c d bcd bcd上的 d d d去,為什麼??
因為 b c d bcd bcd是 a b c d abcd abcd的一個最長字尾,已經匹配了 a b c d abcd abcd,也一定匹配 b c d bcd bcd
然後根據
b
c
d
bcd
bcd的節點
d
d
d的
f
a
i
l
fail
fail跳到
c
d
cd
cd上的
d
d
可以發現,每次通過 f a i l fail fail指標從節點 i i i跳到 j j j,那麼根到 j j j的路徑是根到 i i i路徑的一個最長字尾
既然是字尾,那麼一定會跳到深度比較小的節點去,假如沒有那個點,就跳回根節點
預處理 f a i l fail fail指標
所以根據這個性質,可以使用 b f s bfs bfs的方式預處理 f a i l fail fail指標
首先,根下面的那層字母的 f a i l fail fail指標都指向根節點,因為一個字母的字尾是自己,難道跳到自己去嗎哈哈
然後我們把根節點下面的所有節點入隊 b f s bfs bfs
每次令
u
u
下面這句話有點繞,需要理解一下
u u u兒子的 f a i l fail fail指標等於: u u u的 f a i l fail fail指標指向節點下的對應兒子
(下面三行都是對上面那句話的解釋)
我們這麼想,令 u u u的 f a i l fail fail指標指向 v v v
那麼根節點到 v v v的路徑是根節點到 u u u的路徑的一個字尾
現在根節點是到 u u u的兒子,顯然 v v v後面也需要加一個相同的兒子
這麼做非常正確,但是假如 v v v後面沒有對應的兒子呢??那麼我的 f a i l fail fail指標指向了哪裡呢??
別急,因為這是個 b f s bfs bfs,即使 v v v不存在對應的兒子,但那個位置的 f a i l fail fail指標是被我們處理過的
我們處理一下那個不存在節點的 f a i l fail fail指標即可,這裡看程式碼比較方便
void get_fail()
{
queue<int>q;
for(int i=0;i<26;i++)
if( ac[0].vis[i] ) q.push( ac[0].vis[i] );//第一層的fail指標指向根節點0
while( !q.empty() )
{
int u = q.front(); q.pop();
for(int i=0;i<26;i++)
{
if( ac[u].vis[i] )//有子節點
{
ac[ac[u].vis[i]].fail = ac[ac[u].fail].vis[i];
q.push( ac[u].vis[i] );
}
else//沒有子節點
ac[u].vis[i] = ac[ac[u].fail].vis[i];
//沒有這個子節點,這個子節點指向當前節點fail指標的子節點.
//假如fail指標指向的點也沒有這個兒子,沒關係,在之前的bfs已經處理過了
}
}
}
預處理完畢,開始 A C AC AC
艱難的 f a i l fail fail指標構建完畢,接下來就丟進去暴力匹配就好了
int AC_query( char a[] )
{
int n = strlen( a+1 );
int now = 0, ans = 0;
for(int i=1;i<=n;i++)
{
now = ac[now].vis[a[i]-'a'];//從[1,i]開始利用fail指標,跳躍字尾
for(int t=now;t&&ac[t].end!=-1;t=ac[t].fail)
ans += ac[t].end, ac[t].end = -1;//經過的節點計算了答案,標記起來下次不走
}
return ans;
}
下面是完整程式碼
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6+10;
struct tree
{
int fail,vis[27],end;
}ac[maxn]; int id,n;
char a[maxn];
void insert(char a[] )
{
int n = strlen( a+1 ), now = 0;
for(int i=1;i<=n;i++)
{
if( !ac[now].vis[a[i]-'a'] ) ac[now].vis[a[i]-'a'] = ++id;
now = ac[now].vis[a[i]-'a'];
}
ac[now].end++;
}
void get_fail()
{
queue<int>q;
for(int i=0;i<26;i++)
if( ac[0].vis[i] ) q.push( ac[0].vis[i] );
while( !q.empty() )
{
int u = q.front(); q.pop();
for(int i=0;i<26;i++)
{
if( ac[u].vis[i] )//有子節點
{
ac[ac[u].vis[i]].fail = ac[ac[u].fail].vis[i];
q.push( ac[u].vis[i] );
}
else
ac[u].vis[i] = ac[ac[u].fail].vis[i];
//沒有這個子節點,這個子節點指向當前節點fail指標的子節點
}
}
}
int AC_query( char a[] )
{
int n = strlen( a+1 );
int now = 0, ans = 0;
for(int i=1;i<=n;i++)
{
now = ac[now].vis[a[i]-'a'];
for(int t=now;t&&ac[t].end!=-1;t=ac[t].fail)
ans += ac[t].end, ac[t].end = -1;
}
return ans;
}
int main()
{
cin >> n;
for(int i=1;i<=n;i++)
scanf("%s",a+1),insert( a );
get_fail();
scanf("%s",a+1);
printf("%d",AC_query(a) );
}