HELLO WORLD--一起加油(🍺)!|

kingwzun

园龄:3年6个月粉丝:111关注:0

2022-08-09 10:32阅读: 29评论: 0推荐: 0

字符串 _ AC自动机

概述

算法作用:
O(n)的复杂度 求出多个匹配串 出现在 模式串的 哪些地方 出现次数。

算法核心:
自动机的和kmp 类似,关键是next指针

构建next指针

next指针:状态 u 的 next 指针指向另一个状态 v, 当且仅当 v 是 u 的最长后缀。
即: next 指针是指向所有模式串的前缀中匹配当前状态的最长后缀的状态。

非平凡的后缀:除了自身以外的后缀。
上面的后缀都是非平凡的。

Trie 中的结点表示的是某个模式串的前缀。我们在后文也将其称作状态
一个结点表示一个状态,Trie 的边就是状态的转移。

如图:
image

如何求next呢? 和kmp类似:

如果节点 u 的父亲节点是fa,且fa通过字符 c 的边指向 u ;父亲节点fa的next指向k。
如果我们要找u的next指针,那么我们就看k的子节点

  • 如果 trie[k][c] 存在:u 的 next 就指向 trie[k][c]

  • 如果 trie[k][c] 不存在:就 while(u=next[u]),直到找到相同的或者根节点为止。将u 的 next 就指向 trie[k][c]

代码
我们可以发现我们是用前面层的信息去 更新 新的一层的信息,所以next需要一层一层的获取,因此用bfs。

void build(){
queue<int> q;
//从根的所有儿子开始搜索
for(int i=0;i<26;i++){
if(tr[0][i])
q.push(tr[0][i]);
}
while(q.size()){
int t=q.front();
q.pop();
for(int i=0;i<26;i++){
int c=tr[t][i];
if(!c) continue;//如果节点不存在continue
int j=ne[t];
//如果next指针指到的位置的儿子不存在;
//这个字符那么就一直往回找直到找到为止
while(j && !tr[j][i]) j=ne[j];
//next数组等于找到的节点j的next
if(tr[j][i]) j=tr[j][i];
ne[c]=j;
q.push(c);
}
}
}

匹配过程

和kmp类似:查找过程和构建过程差不多。
注意几点:

  1. 如果一个字符串匹配成功,那么他的后缀也一定可以匹配成功。
    比如说:成功匹配到she:那么所有she的后缀(he,e)也一定可以匹配

  2. 为了避免重复计算,我们将经过的cnt[u]设为0。
    cnt数组是有多少以此节点为结尾的匹配串

代码:

cin>>str;
int ans=0;
for(int i=0,j=0;str[i];i++){
int t=str[i]-'a';
//
while(j && !tr[j][t]) j=ne[j];
if(tr[j][t]) j=tr[j][t];
int p=j;
//回溯匹配串的后缀
while(p){
ans+=cnt[p];
cnt[p]=0;//cnt数组是有多少以此节点为结尾的匹配串
p=ne[p];
}
}
cout<<ans<<endl;

Trie图优化

我们可以发现代码上面里面有个while循环,虽然while还是常数,但是我们希望可以优化掉。

证明while是常数
待补

优化思路:
在没有匹配时 把while循环多次跳 优化为 直接跳到ne指针最终跳到的位置。从而去掉while循环。

优化方式: 直接记住吧
每次取出队首的结点u (fail[u]在之前的 BFS 过程中已求得),然后遍历字符集u的各个子节点:

  1. 如果 trans[u][i] 存在,我们就将 trans[u][i]next 指针赋值为 trans[next[u]][i]
  2. 否则,令trans[u][i] 指向 trans[next[u]][i] 的状态。

oiwikl的解释

image

严格鸽 的解释
image

类似于并查集的路径压缩,我们可以建成Trie图。
(因为会使Trie原本的DAG结构出现环。)

构建代码:

void build(){
queue<int> q;
//从根的所有儿子开始搜索
for(int i=0;i<26;i++){
if(tr[0][i])
q.push(tr[0][i]);
}
while(q.size()){
int t=q.front();
q.pop();
for(int i=0;i<26;i++){
int p=tr[t][i];
if(!p) tr[t][i]=tr[ne[t]][i];
else{
ne[p]=tr[ne[t]][i];
q.push(p);
}
}
}
}

遍历时只需要直接访问即可

int ans=0;
for(int i=0,j=0;str[i];i++){
int t=str[i]-'a';
j=tr[j][t];
int p=j;
while(p){
ans+=cnt[p];
cnt[p]=0;
p=ne[p];
}
}

原文1
原文2
原文3

模板代码

#include <bits/stdc++.h>
using namespace std;
const int N=10010,S=55;
int n;
string str;
int tr[N*S][26],cnt[N*S],idx;
int ne[N*S];
void insert(){
int p=0;
for(int i=0;i<str[i];i++)
{
int t=str[i]-'a';
if(!tr[p][t]) tr[p][t]=++idx;
p=tr[p][t];
}
cnt[p]++;
}
void build(){
queue<int> q;
for(int i=0;i<26;i++){
if(tr[0][i])
q.push(tr[0][i]);
}
while(q.size()){
int t=q.front();
q.pop();
for(int i=0;i<26;i++){
int p=tr[t][i];
if(!p) tr[t][i]=tr[ne[t]][i];
else{
ne[p]=tr[ne[t]][i];
q.push(p);
}
}
}
}
void solve(){
memset(tr,0,sizeof tr);
memset(cnt,0,sizeof cnt);
memset(ne,0,sizeof ne);
idx=0;
cin>>n;
for(int i=0;i<n;i++){
cin>>str;
insert();
}
build();
cin>>str;
int ans=0;
for(int i=0,j=0;str[i];i++){
int t=str[i]-'a';
j=tr[j][t];
int p=j;
while(p){
ans+=cnt[p];
cnt[p]=0;
p=ne[p];
}
}
cout<<ans<<endl;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t;cin>>t;
while(t--)
solve();
return 0;
}

应用

本文作者:kingwzun

本文链接:https://www.cnblogs.com/kingwz/p/16565077.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   kingwzun  阅读(29)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起