• 并不对劲的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自动机上做了。。。

  • 相关阅读:
    云计算、大数据和人工智能简单概述
    Linux 文件管理命令语法、参数、实例全汇总(一)
    C#之继承
    类和结构(二)
    类和结构(一)
    C#基础语法(二)
    C#基础语法(一)
    dotnet体系结构
    九卷读书:刘润商学院学习笔记1
    Linux内存管理
  • 原文地址:https://www.cnblogs.com/xzyf/p/8244527.html
Copyright © 2020-2023  润新知