AC自动机
前置知识点:KMP算法,[Trie字典树(暂时没做]
模板题:
算法简介
区别于KMP算法,AC自动机应用于多模式字符串匹配,即同时对一个匹配串匹配多个模式串
考虑暴力匹配
类比于我们推到kmp的过程,我们先考虑最暴力的情况
假如我们对模式串建立一棵Trie树,可以做到O(n^2)的时间复杂度匹配以每一个字符为起点的匹配串后缀是否能匹配到一个模式串
AC自动机:
考虑kmp的优化,我们能不能在trie树上找到一个类似于nxt数组的东西,当我们匹配失败是通过nxt数组回溯到非根节点的位置,以此节省复杂度
考虑trie树的定义,根节点到所有节点的路径构成了互不相同的字符串集S,我们定义一个失配指针用数组it表示
\(it_i\)表示i节点在trie上最长的于\(S_i\)后缀相同的前缀节点,比如,当我们在i节点匹配失败时,我们回退到\(it_i\)节点继续匹配
这样,我们就可以类比于kmp的匹配在trie树上O(|a|) 的匹配和查找失配指针,trie树加上适配指针即为AC自动机
当然,不建议看到这里的童鞋先去把模板写了如果你提前写了,你会发现写出来又臭又长,而且会把标准模板T掉
优化:
你已经学会了什么是AC自动机,剩下的其实算不上优化,只是写法更简便
很明显,在一颗trie树上回退是个很麻烦恶心的操作,我们可以考虑优化掉,我们定义一个匹配边,他直接指向对应节点匹配对应子节点失败时回退到的节点,假如我们在i节点匹配p儿子失败时,直接通过i->p儿子的匹配边匹配到下一位,跨过回退过程
具体而言,匹配边有一下作用和求解方式
我们定义ch[i][j]表示i节点的第j个子节点
作用:除了我们上文提到的匹配时的作用,在我们考虑到一个节点i后,对于他的一个非空子节点j的失配指针一定等于ch[i][j]
求解:当我们考虑到一个节点i后,对于他的空子节点j,我们可以建立匹配边使ch[i][j]=ch[it[i]][j]
对于上述过程,读者对着样例手模一边就能理解了
失配指针的基础性质
学习完上述过程后,我们还是做不了AC自动机模板,因为我们还有一个失配指针最基础也是最重要的性质没讲
证明:i节点的失配指针指向j节点,说明j节点对应的字符串\(S_j\)是\(S_i\)的子串,而\(S_j\)是模式串,所以\(S_i\)的前缀为对应的模式串
我们也可以理解为,当我们匹配完i节点后,就相当于匹配了j节点
有了这一性质以后,我们就可以求解每一个模式串在匹配串中出现的次数了
我们在匹配时记录每个节点被匹配的此时vis,将失配指针看作有向边(回退节点指向当前节点),我们在DAG上跑拓补排序,用一个简单的递推就能得出每个节点被匹配的次数,接下来直接统计答案即可
标准模板ACcode:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define MAXN 200010
inline ll read(){
ll x=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){
if(c=='-')f=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
x=x*10+c-'0';
c=getchar();
}
return x*f;
}
struct node_AC{
ll ch[MAXN][30],it[MAXN],ins[MAXN],vis[MAXN];
ll idx,siz,tp;
ll ans[MAXN],maxz;
string sp[MAXN],vec[MAXN];
vector <ll> ed[MAXN];
void insert(string s){
ll len=s.length(),p=0;
sp[++siz]=s;
s="0"+s;
for(int i=1;i<=len;i++){
ll y=s[i]-'a'+1;
if(ch[p][y])p=ch[p][y];
else ch[p][y]=++idx,p=idx;
}
// ed[p]=siz;
ed[p].push_back(siz);
}
void deal(){
queue<ll> q;
for(int i=1;i<=26;i++){
if(ch[0][i]){
it[ch[0][i]]=0;
q.push(ch[0][i]);
}
}
while(!q.empty()){
ll now=q.front();
q.pop();
ins[it[now]]++;
for(int i=1;i<=26;i++){
if(ch[now][i]){
it[ch[now][i]]=ch[it[now]][i];
q.push(ch[now][i]);
// ins[ch[it[now]][i]]++;
}else ch[now][i]=ch[it[now]][i];
}
}
}
void match(string s){
ll len=s.length(),p=0;
s="0"+s;
for(int i=1;i<=len;i++){
ll y=s[i]-'a'+1;
p=ch[p][y];
// dfs(p);
// ans[ed[p]]++;
// for(int j=0;j<ed[p].size();j++)ans[ed[p][j]]++;
vis[p]++;
}
}
void topsort(){
queue<ll> q;
for(int i=1;i<=idx;i++)if(!ins[i])q.push(i);
while(!q.empty()){
ll p=q.front();
q.pop();
for(int i=0;i<ed[p].size();i++)ans[ed[p][i]]+=vis[p];
vis[it[p]]+=vis[p];
ins[it[p]]--;
if(!ins[it[p]])q.push(it[p]);
}
}
void write(){
topsort();
for(int i=1;i<=siz;i++){
cout<<ans[i]<<endl;
}
// clear();
}
};
node_AC trie;
ll n;
string s;
int main(){
cin>>n;
if(n==0)return 0;
for(int i=1;i<=n;i++){
cin>>s;
trie.insert(s);
}
trie.deal();
cin>>s;
trie.match(s);
trie.write();
return 0;
}

浙公网安备 33010602011771号