题意 多个 B串 匹配 A 串
学完ac自动机,发现其实它与KMP有异曲同工之妙。
我们知道,KMP要先对B串进行自我匹配求出 F 数组,同样的,AC自动机也要对所有B串进行自我匹配。
区别在于KMP中,B串只有一个,所以我们直接循环进行,但是ac自动机中B串有多个,每个都用循环处理的话时间复杂度不支持,所以我们考虑用trie树,在trie树上建立类似于KMP中的 F[ n ] 数组;
首先复习一下 F 数组的意义 F[ i ] 表示在trie书上 从根节点开始以 i 为结尾的串的后缀 与 根节点到 F[ i ] 代表的子串相同 (当前串的某些后缀 与某些串的前缀相同)
怎么求出 F 数组呢 我们使用BFS的方法
现将根节点的存在的子节点放入队列中
然后依次取出队首,对于当前节点now,我们遍历它的a-z这些子节点c[now][a-z],假设存在 c[now][a],那么将这个子节点c[now][a]的 F 指向 当前F[now] 的子节点 a。 即F[ c[now][a] ] = c[ F[now] ][a], 然后令u节点入队 。 这样便能满足后缀等于前缀。(想想清楚)
对于不存在的 c[now][a] 令 c[now][a]=c[ F[now] ][ a ]
假设A串下一个字母是 a 而,当前节点 now 的 子节点不存在 c[now][a] ,那么匹配失败,按照推理需要跳到c[ f[now] ][a],所以便有了 now没有子节点a时 c[now][a]=c[ F[now] ][a];
void ins(char *s){
int len=strlen(s);
int now=0;
for(int i=0;i<len;i++){
int u=s[i]-'a';
if(!c[now][u])c[now][u]=++cnt;
now=c[now][u];
}
val[now]++;
}
void build(){
for(int i=0;i<26;i++)if(c[0][i])fail[c[0][i]]=0,q.push(c[0][i]);
while(!q.empty()){
int x=q.front();q.pop();
for(int i=0;i<26;i++){
if(c[x][i])fail[c[x][i]]=c[fail[x]][i],q.push(c[x][i]);
else c[x][i]=c[fail[x]][i];
}
}
}
这样我们便成功建立了F 数组 并且 完善了trie树
下面便是遍历A串了
假设遍历到了now 下一个字母是a 那么 now=c[now][a]
到达了 新的节点 我们要统计这是否是一个单词的结尾 ,同时 我们还要统计 F[now] 是否是单词的结尾 还有F[ F[now] ] 是否是单词的结尾, 一直到根节点 。
为什么呢, 因为到达当前节点now 如果有标记,也只能说根节点到now代表了一个B串 那么是否有可能 根节点到now路径上的 某一个点u 到now也代表了一个B串呢,显然是有可能的,s[u-now] 这个子串我们插入的时候是从根节点开始的。
而这个节点 u 到 now 是 以now为结尾的串的后缀 刚好就能够满足 F[now]的定义。
所以每走一个点,我们要判断 一直F[now]直到根节点
int query(char *s){
int len=strlen(s);
int ans=0,now=0;
for(int i=0;i<len;i++){
int u=s[i]-'a';
now=c[now][u]; // 走 到now节点
for(int t=now;t&&val[t]!=-1;t=fail[t])ans+=val[t],val[t]=-1;
要依次判断 F[now] 是不是某个单词的结尾 累加答案直到根节点
}
return ans;
}