并不对劲的AC自动机

 这像是能解决所有问题的样子(并不)。AC自动机之所以叫AC自动机是因为它能解决所有AC自动机的题。

其实只能解决的是很多模式串匹配一个母串的问题。

把kmp中的next数组得到下一次跳转的位置看成特殊的边,把字符串看成链,就会得到一个特殊的图。

一个点u的next连向点v对应的字符串是u最长的后缀,把这个图套用到很多个模式串组成的trie上。

对于点u能走到的节点ch[u][i],相当于在u对应的字符串后补了一个字符i。那么点u的next连向的点v对应的字符串后面再补一个字符i就是点ch[u][i]的next。有时并没有ch[v][i],那么就要找v的next也就是更短的后缀。要是一直找不到,就只能连向0号点(空串)了。算next是一定要用bfs,不然会出现要走u的next却发现u的next还没有被算出的尴尬情况。

匹配时,如果没有ch[u][i]就要走u的next。还要注意的是,能走到u说明也能走到u的next、u的next的next、u的next的next的next等,要把这些点的值也加上。这样不会TLE吗?想必是不会的。要想知道一堆字符串中有多少个是某个串的子串,走到每个字符串结尾代表的节点时,只能统计一次。那么在统计时可以进行标记,被标记的点就不走了。又因为当一个点被标记时,它的next、它的next的next等都已经被统计过了,所以当顺着next统计的时候,发现一个被标记的点就可以停止了。自动机上的每一个点只会走到一次,所以并不会TLE。

考虑next很麻烦?当ch[u][i]==0时,令ch[u][i]=next!

#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iomanip>
#include<iostream>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#define maxn 1000010
#define rep(i,x,y) for(register int i=(x);i<=(y);i++)
#define dwn(i,x,y) for(register int i=(x);i>=(y);i--)
#define vv ch[u][i]
using namespace std;
inline int read()
{
    int x=0,f=1;
    char ch=getchar();
    while(isdigit(ch)==0 && ch!='-')ch=getchar();
    if(ch=='-')f=-1,ch=getchar();
    while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
    return x*f;
}
inline void write(int x)
{
    int f=0;char ch[20];
    if(!x){puts("0");return;}
    if(x<0){putchar('-');x=-x;}
    while(x)ch[++f]=x%10+'0',x/=10;
    while(f)putchar(ch[f--]);
    putchar('
');
}
int ch[maxn][30],val[maxn],fail[maxn],cnt=0,n,len;
char s[maxn];
int gx(char c){return c-'a';}
void build()
{
    int u=0;
    rep(i,0,len-1)
    {
        if(!ch[u][gx(s[i])])ch[u][gx(s[i])]=++cnt;
        u=ch[u][gx(s[i])];
    }
    val[u]++;
}
void getfail()
{
    fail[0]=0;
    queue<int >q;
    rep(i,0,25)
        if(ch[0][i])
            q.push(ch[0][i]),
            fail[ch[0][i]]=0;
    while(!q.empty())
    {
        int u=q.front();q.pop();
        rep(i,0,25)
        {
            if(vv)
                q.push(vv),fail[vv]=ch[fail[u]][i];
            else vv=ch[fail[u]][i];
        }
    }
}
int getans()
{
    int ans=0,u=0;
    rep(i,0,len-1)
    {
        u=ch[u][gx(s[i])];
        for(int j=u;j&&val[j]!=-1;j=fail[j])
            ans+=val[j],val[j]=-1;
    }
    return ans;
}
int main()
{
    n=read();
    rep(i,1,n)
        scanf("%s",s),len=strlen(s),build();
    getfail();
    scanf("%s",s),len=strlen(s);write(getans());
    return 0;
}
View Code
#include<set>
#include<map>
#include<cmath>
#include<queue>
#include<stack>
#include<ctime>
#include<string>
#include<cstdio>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define LL long long
#define MAXN 1000+1000000
#define INF 2147483647

using namespace std;
inline int read(){
    int x=0,f=1; char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
    for(;isdigit(ch);ch=getchar())x=ch-'0'+(x<<3)+(x<<1);
    return x*f;
}

char str[MAXN];
struct ACAutomata{
    int ch[MAXN][31],val[MAXN],size;
    queue<int>Q; int fail[MAXN],vis[MAXN];
    void insert(){
        int cur=0,len=strlen(str);
        for(int i=0;i<len;i++){
            if(ch[cur][str[i]-'a'])cur=ch[cur][str[i]-'a'];
            else ch[cur][str[i]-'a']=++size,cur=size;
        }
        val[cur]++; return ;
    }
    
    void calc(){
        while(!Q.empty()){
            int u=Q.front(); Q.pop();
            for(int i=0;i<26;i++){
                if(!ch[u][i])continue;
                int cur=fail[u]; Q.push(ch[u][i]);
                while(!ch[cur][i]&&cur)cur=fail[cur];
                fail[ch[u][i]]=ch[cur][i];
            }
        }
        return ;
    }
    
    int solve(){
        int cur=0,ans=0,len=strlen(str),pos=0;
        while(pos<len){
            for(int i=cur;!vis[i]&&i!=0;i=fail[i])
                ans+=val[i],vis[i]=1; 
            if(ch[cur][str[pos]-'a'])cur=ch[cur][str[pos]-'a'];
            else {
                cur=fail[cur];
                while(!ch[cur][str[pos]-'a']&&cur)cur=fail[cur];
                cur=ch[cur][str[pos]-'a'];
            }
            for(int i=cur;!vis[i]&&i!=0;i=fail[i])
                ans+=val[i],vis[i]=1; pos++;
        }
        return ans;
    }
}T;

int main(){
    int n=read();
    for(int i=1;i<=n;i++){scanf("%s",str);T.insert();}
    for(int i=0;i<=25;i++)if(T.ch[0][i])T.Q.push(T.ch[0][i]);
    T.calc(); scanf("%s",str); printf("%d",T.solve());
    return 0;
}
View Code

别人一般把这种并不对劲的next称为失配边、fail之类的。

刚刚说过这是一个特殊的图,因为每个点只会连出一条失配边,所有失配边也组成了一棵树。

这样树上的某些坑人操作也可以在AC自动机上做了。。。

原文地址:https://www.cnblogs.com/xzyf/p/8244527.html