匹配多个字符串——AC自动机

来源:互联网 发布:网络剧《余罪》 编辑:程序博客网 时间:2024/05/29 19:41

真·AC自动机:zyy
AC自动机这个东西。。名字真的是没得吐槽=_=
它运用了kmp算法的next思想(体现于后文的fail指针)以及trie的数据结构(字母树)
kmp如果不会的话还可以,trie不会的话。。建议还是先去学trie吧
我看了好久。。才看懂这个蜜汁自动机


先看一下模版题来了解AC自动机的用处:
Hdu2222
题意:有n段小字符串,求其中有多少段出现在了一个大字符串中
题目应该不难理解
那么怎么做?
朴素想法:每一个字符串去大字符串中kmp一遍之间效率O(n*len)
。。妥妥tle

对于这种问题,我们就需要用到AC自动机
AC自动机=trie+fail
所谓fail指针,就是一个类似于kmp中的next,用于匹配失败时快速跳转。
虽然不知道为什么别的为什么每个例子都一样。。那我也一样好了
大概是什么 she he shr say her..??
然后大字符串: yasheshr?
差不多就这样…
我们先把所有小字符串放到trie中,这一步不详细解释了

inline void insert(string ts){    int len=ts.length();    int p=0;    For(i,0,len-1)    {        int c=ts[i]-'a';        if(!nxt[p][c])  nxt[p][c]=++tot;        p=nxt[p][c];    }    cnt[p]++;}

接下来我们考虑一下大字符串在trie上的询问
一条一条走下去。。走不了了就回溯?
累加各个结束标记?
时间效率。。O(结点数)
好像和直接kmp没什么太大局别- -…
但是其实有很多回溯都是没有用的
很多很多!!
不只是这样
我们根据刚才的输入构建出来的trie应该是这样的:
这里写图片描述
我们搜完she后,如果是用回溯,就会找不到r在哪=_=
所以不只是时间上,连思想上都退化成了枚举。。
那么要怎么办?
我们可不可以在搜完she后直接跳到he后面那个r呢
。。。这个想法似曾相识对吧= =
没错它就是kmp
fail指针就是这个用处,直接指向he后面那个r
这个其实。。说实话,构造过程只可意会不可言传
好吧我还是言传一下 不然要被打
一开始,所有节点的fail指针指向root(根节点)
我们如果要更新节点t的fail,那么我们就沿着t的父亲的fail指针一直走啊走,走到一个节点有t这个字母的儿子
比如说我们要找上图h的fail
这里写图片描述
我们通过S的fail询问到根节点,发现其有一个是叫做 H的儿子
那么我们就把第三层的h的fail指向第二次的那个h
这里写图片描述
同样,我们要找E的fail
这里写图片描述
通过E的父节点H的fail,找到第二层的H,发现其有一个儿子为E,就可以更新fail指针了
这里写图片描述
啊。。吐血
还是看代码理解一下吧,这个真的不知道应该怎么说
补:lc233大佬说,为什么不能用dfs求,那么下面我放两张图大家理解一下。。
这里写图片描述
然后我们要求最深的那个E的fail
此时更新了第一条链的fail
这里写图片描述
这就是为什么dfs不可行。。。

inline void build(){    int t,l=1,r=1;    fail[root]=0;    q[1]=root;    while(l<=r)    {        t=q[l];        int p=0;        For(i,0,25)        {            if(nxt[t][i])            {                if(t==root){fail[nxt[t][i]]=root;}                else                {                    p=fail[t];                    while(p)                    {                        if(nxt[p][i])                        {                            fail[nxt[t][i]]=nxt[p][i];                            break;                        }                        p=fail[p];                    }                    if(!p)                      {                        if(nxt[p][i]) fail[nxt[t][i]]=nxt[p][i];                        else fail[nxt[t][i]]=root;                    }                }                q[++r]=nxt[t][i];            }        }        l++;    }    For(i,1,r)  q[i]=0;}

然后询问的时候,我们只需要一路搜下去,如果匹配失败就往fail那边跑,一路把结束标记全部加起来。。就是答案了
要注意!重复的千万不能加!
当我们加到一个点时,可以用一个while循环将他所有fail指针上的end指针加上去

inline void query(string s){    int len=s.length(),sum=0,p=0;    For(i,0,len-1)    {        int c=s[i]-'a';        while(nxt[p][c]==0&&p)  p=fail[p];        p=nxt[p][c];        int t=p;        while(t&&cnt[t]!=-1)        {            sum+=cnt[t];            cnt[t]=-1;            t=fail[t];        }    }    printf("%d\n",sum);}

好了那么大致思路就是这样了
附HDU2222 AC代码

#include<iostream>#include<cstdio>#include<cmath>#include<algorithm>#include<queue>#include<string>#include<cstring>#define inf 1e9#define ll long long#define For(i,j,k) for(int i=j;i<=k;i++)#define Dow(i,j,k) for(int i=k;i>=j;i--)using namespace std;int n,nn,nxt[1000001][30],q[1000001],fail[1000001],cnt[1000001],tot,root=0;inline void insert(string ts){    int len=ts.length();    int p=0;    For(i,0,len-1)    {        int c=ts[i]-'a';        if(!nxt[p][c])  nxt[p][c]=++tot;        p=nxt[p][c];    }    cnt[p]++;}inline void build(){    int t,l=1,r=1;    fail[root]=0;    q[1]=root;    while(l<=r)    {        t=q[l];        int p=0;        For(i,0,25)        {            if(nxt[t][i])            {                if(t==root){fail[nxt[t][i]]=root;}                else                {                    p=fail[t];                    while(p)                    {                        if(nxt[p][i])                        {                            fail[nxt[t][i]]=nxt[p][i];                            break;                        }                        p=fail[p];                    }                    if(!p)                      {                        if(nxt[p][i]) fail[nxt[t][i]]=nxt[p][i];                        else fail[nxt[t][i]]=root;                    }                }                q[++r]=nxt[t][i];            }        }        l++;    }    For(i,1,r)  q[i]=0;}inline void query(string s){    int len=s.length(),sum=0,p=0;    For(i,0,len-1)    {        int c=s[i]-'a';        while(nxt[p][c]==0&&p)  p=fail[p];        p=nxt[p][c];        int t=p;        while(t&&cnt[t]!=-1)        {            sum+=cnt[t];            cnt[t]=-1;            t=fail[t];        }    }    printf("%d\n",sum);}inline void doit(){    For(i,0,tot)        For(j,0,25) nxt[i][j]=0;    For(i,0,tot)    fail[i]=cnt[i]=0;    tot=root=0;    cin>>n;    For(i,1,n)    {        string ts;        cin>>ts;        insert(ts);    }    build();    string s;    cin>>s;    query(s);}int main(){    ios::sync_with_stdio(false);    cin>>nn;    For(ii,1,nn)    {        doit();    }}
4 0
原创粉丝点击