Loading

AC自动机学习笔记

来补字符串的知识点了,,

ac自动机可以用来解决这样的问题:

给定多个模式串和一个长文本,求每个模式串在文本中出现的次数。

算法的核心是在trie树上建立fail边,每次失配的时候沿着fail边跳到另外的节点上,fail边建立当且仅当连向的节点在trie树上的前缀是原来节点在trie树上的前缀的后缀。写起来是这样的:

int fl=ac[u].fail;
for(int i=0;i<=26;i++){
	if(!ac[u].vis[i]){ac[u].vis[i]=ac[fl].vis[i];continue;}
  ac[ac[u].vis[i]].fail=ac[fl].vis[i];
  rd[ac[ac[u].vis[i]].fail]++;
  k1.push(ac[u].vis[i]);
}

那么如果没有向下的节点了,则父节点向某字母子节点的路连向父节点的fail节点的对应字母子节点;

如果有,则子节点创建一条fail路连向父节点的fail节点的对应字母子节点。

最简单的ac自动机是暴力跳fail的,最差情况下(模式串+文本串是aaaaaaaa这样的////)可以被卡成O(模式串长度*文本串长度),这是不可接受的。

优化方法是:在匹配过的节点上打上标记,然后除非失配时跳向对应的fail节点,其余时候先不跳fail了。

等到匹配了一遍之后我们得到了一颗fail树,树上的一些节点打上了标记,简单版的跳fail操作实际上就是在打上的标记的点上继续沿着fail树向下加上贡献。那么这个时候我们只需要拓扑排序或者bfs一遍把打上标记的点的贡献更新到所有节点上即可。

例题:洛谷P5357

#include <bits/stdc++.h>
using namespace std;
const int maxn=2e6+10;
struct aczdj{
    int fail,vis[30],flag,ans1;
}ac[maxn];
int cnt=1,n;
int rd[maxn],ans[200010],mp[maxn];
queue<int> k1;
void build(string &now,int tp){
    int now1=now.length(),now2=1;
    for(int i=0;i<now1;i++){
        int v=now[i]-'a';
        if(!ac[now2].vis[v]) ac[now2].vis[v]=++cnt;
        now2=ac[now2].vis[v];
    }
    if(!ac[now2].flag) ac[now2].flag=tp;
    mp[tp]=ac[now2].flag;
}
void buildfail(){
    for(int i=0;i<26;i++) ac[0].vis[i]=1;////
    k1.push(1);
    while(!k1.empty()){
        int u=k1.front();k1.pop();
        int fl=ac[u].fail;
        for(int i=0;i<26;i++){
            if(!ac[u].vis[i]){ac[u].vis[i]=ac[fl].vis[i];continue;}
            ac[ac[u].vis[i]].fail=ac[fl].vis[i];
            rd[ac[ac[u].vis[i]].fail]++;
            k1.push(ac[u].vis[i]);
        }
    }
}
void topo(){
    for(int i=1;i<=cnt;i++){
        if(rd[i]==0) k1.push(i);
    }
    while(!k1.empty()){
        int tmp=k1.front();k1.pop();
        ans[ac[tmp].flag]=ac[tmp].ans1;
        int v=ac[tmp].fail;
        rd[v]--;
        ac[v].ans1+=ac[tmp].ans1;
        if(rd[v]==0) k1.push(v);
    }
}
void acque(string &now){
    int now1=now.length(),now2=1;
    for(int i=0;i<now1;i++){
        now2=ac[now2].vis[now[i]-'a'];
        ac[now2].ans1++;
    }
}
signed main(){
    cin>>n;
    string yuan,all;
    for(int i=1;i<=n;i++){
        cin>>yuan;
        build(yuan,i);
    }
    buildfail();
    cin>>all;
    acque(all);
    topo();
    for(int i=1;i<=n;i++) cout<<ans[mp[i]]<<endl;
}
posted @ 2021-07-11 16:40  14long  阅读(36)  评论(0)    收藏  举报