題目鏈接:https://www.luogu.org/problem/P3808
AC自動機是看http://blog.c0per.org/2018-10/ac/和https://www.luogu.org/blog/juruohyfhaha/ac-zi-dong-ji看懂的,理解怎麼操作不難,但是最主要要理解的是爲什麼這樣子操作。
首先AC自動機是在字典樹的基礎上加了一些類似kmp的思想,但是kmp是相同前後綴,這裏的失配指針是相同後綴,kmp用next指針進行跳轉,AC自動機是用一個失配指針fail進行跳轉。
構建失配指針的操作:
對於節點i,找它父親節點的fail指針指向的那個節點j,看j的子節點中有沒有和i節點相同的:
如果有,i節點的failz指針就指向j的那個子節點;
如果沒有,繼續找節點j的父親節點的失配指針指向的節點循環
然後就是爲什麼要這麼操作,構建完後的樹是這樣子的:
假如模式串是shey,以第3個節點爲例:
當我們遍歷到第3個節點時,到目前位置都匹配成功,並且節點3是第一個串的末尾,cntword[3]的值不爲0,所以ans得到增加;然後就上跳到它的fail指針5,5是3的失配節點意味着,第二個串在第5個節點之前都是第一個串到第3個節點的後綴,也就是第二個串的he是第一個串she的後綴,那麼she已知匹配,he就一定能匹配成功,但5不是第二個串的末尾,cntword[5]=0,ans沒有變化;然後繼續跳到5的失配節點7,第3個串到第7個節點也一定是匹配的,且7是第三個串的末尾,ans得到增加。
然後到了下一層,因爲第3個節點實際上是沒有y這個子結點的,但是因爲它的失配指針指向的節點5有y這個子結點,所以我們在構建失配指針時,就已經將第3個節點的y子結點指向了節點6,同理這樣子跳轉過來本身就已經保證了節點6之前都是匹配成功的。
#include<iostream>
#include<cstring>
#include<string>
#include<queue>
using namespace std;
const int maxn=1e6+10;
int cnt=0;
int trie[maxn][26];//trie[i][j]表示的當前字母爲i+'a',且父節點編號爲i的節點的編號
int cntword[maxn];
int fail[maxn];
void insert(string s)
{
int root=0;
for(int i=0;i<s.size();i++)
{
int next=s[i]-'a';
if(!trie[root][next])
trie[root][next]=++cnt;
root=trie[root][next];
}
cntword[root]++;//當前節點的單詞數+1,而不是等於1,因爲可能重複。
//這時的root是單詞的末尾的編號,表示的是一整個單詞,途中經歷過的點的cntword是沒有自增的;
}
void getFail()//層序遍歷
{
queue<int>q;
for(int i=0;i<26;i++)
if(trie[0][i])//首先要先將最上面一層出現的字母(每個串的首字符)入隊
{
fail[trie[0][i]]=0;
q.push(trie[0][i]);
}
while(!q.empty())
{
int now=q.front();
q.pop();
for(int i=0;i<26;i++)
{
if(trie[now][i])//通過枚舉26個字母來找到now的子節點們
{
//該節點i的失敗指針指向它父節點now的失敗指針fail[now]的和i節點相同的節點
fail[trie[now][i]]=trie[fail[now]][i];
q.push(trie[now][i]);
}
else
trie[now][i]=trie[fail[now]][i];//如果不存在這個節點就上跳到失配節點的等於i的這個子節點上
}
}
}
int query(string s)
{
int now=0,ans=0;
for(int i=0;i<s.size();i++)
{
now=trie[now][s[i]-'a'];
for(int j=now;j&&cntword[j]!=-1;j=fail[j])//當前節點匹配完成或失配時就上跳fail指針
{
ans+=cntword[j];//只有j是某個單詞末尾時纔有值
cntword[j]=-1;//記錄過的節點要標記,防止重複
}
}
return ans;
}
int main()
{
int n;
while(cin>>n)
{
cnt=0;
memset(cntword,0,sizeof(cntword));
memset(fail,0,sizeof(fail));
memset(trie,0,sizeof(trie));
string a,s;
for(int i=1;i<=n;i++)
{
cin>>a;
insert(a);
}
getFail();
cin>>s;
cout<<query(s)<<endl;
}
return 0;
}