【字符串】AC自动机

AC自动机

学习资料: OI Wiki

模板

解释 fail 指针:

将构建后的字典树的所有状态集合记作 \(Q\)

状态 \(u\) 的 fail 指针指向另一个状态 \(v\) ,其中 \(v\in Q\) ,且 \(v\)\(u\) 的最长后缀(即在若干个后缀状态中取最长的一个作为 fail 指针)。

AC自动机在做匹配时,同一位上可匹配多个模式串。

struct AC{
    int tr[maxn][26],cnt;
    int fail[maxn];
    
    
    int ed[maxn],num[maxn];//这两个是根据题意写的
    
    void init(){
        cnt=0;
        mem(tr,0);mem(fail,0);
        
        mem(ed,0);mem(num,0);
    }
    void insert(char s[],int len,int id)
    {
        int p=0;
        for(int i=0;i<len;i++)
        {
            if(!tr[p][s[i]-'a'])tr[p][s[i]-'a']=++cnt;
            p=tr[p][s[i]-'a'];
        }
        
        ed[p]=id;//对于字符串结尾节点的记录,根据题意记录 如这是记录该字符串的下标(属于第几个字符串)
        // 根据题意,有时是单纯的bool标记,有时是记录长度 等等
    }
    
    void getfail()// 当完成插入时,需要 ac.getfaile() 跑一次。
    {
        queue<int>que;
        for(int i=0;i<26;i++)if(tr[0][i])que.push(tr[0][i]);
        while(!que.empty())
        {
            int p=que.front();que.pop();
            for(int i=0;i<26;i++)
            {
                if(tr[p][i])fail[tr[p][i]]=tr[fail[p]][i],que.push(tr[p][i]);
                else tr[p][i]=tr[fail[p]][i];
            }
        }
    }
    
    // query 是文本串的匹配,根据题意写。
    // 但无外乎就是这些操作
    void query(char s[],int len,int n)
    {
        int j,p=0;
        for(int i=0;i<len;i++)
        {
            j=p=tr[p][s[i]-'a'];
            // while跑的是 以当前后缀不断遍历字典树里的所有相同后缀 
            while(j)
            {
                if(ed[j])
                    num[ed[j]]++;//这一句根据题意写
                j=fail[j];
            }
        }
        //之后的操作也是根据题意
    }
}ac;

例题

1,luoguP3796【模板】AC自动机(加强版)

题意:给 \(n\) 个模式串,之后给一个文本串。对每组数据,第一行输出模式串最多出现的次数,接下去若干行输出一个出现次数最多的模式串,按驶入顺序排列。

解:

对于每个状态终点记录一下该终点对应哪个模式串。之后遍历文本串的时候吗,在fail转移的时候,对每个状态终点记录其对应的出现次数。

#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long ll;
const int maxn=1e6+50;

char s[maxn];
char ss[220][110];
struct AC{
    int tr[maxn][26],cnt;
    int fail[maxn];
    int ed[maxn],num[maxn];
    void init(){
        cnt=0;
        mem(tr,0);mem(fail,0);
        mem(ed,0);mem(num,0);
    }
    void insert(char s[],int len,int id)
    {
        int p=0;
        for(int i=0;i<len;i++)
        {
            if(!tr[p][s[i]-'a'])tr[p][s[i]-'a']=++cnt;
            p=tr[p][s[i]-'a'];
        }
        ed[p]=id;
    }
    void getfail()
    {
        queue<int>que;
        for(int i=0;i<26;i++)if(tr[0][i])que.push(tr[0][i]);
        while(!que.empty())
        {
            int p=que.front();que.pop();
            for(int i=0;i<26;i++)
            {
                if(tr[p][i])fail[tr[p][i]]=tr[fail[p]][i],que.push(tr[p][i]);
                else tr[p][i]=tr[fail[p]][i];
            }
        }
    }
    void query(char s[],int len,int n)
    {
        int j,p=0;
        for(int i=0;i<len;i++)
        {
            j=p=tr[p][s[i]-'a'];
            while(j)
            {
                if(ed[j])num[ed[j]]++;
                j=fail[j];
            }
        }
        int ma=0;
        for(int i=1;i<=n;i++)ma=max(ma,num[i]);
        printf("%d\n",ma);
        for(int i=1;i<=n;i++)if(num[i]==ma)puts(ss[i]);
    }
}ac;
int main()
{
    int n;
    while(scanf("%d",&n)&&n)
    {
        ac.init();
        for(int i=1;i<=n;i++)
        {
            scanf("%s",ss[i]);
            ac.insert(ss[i],strlen(ss[i]),i);
        }
        ac.getfail();
        scanf("%s",s);
        ac.query(s,strlen(s),n);
    }
}

2,牛客 破忒头的匿名信

题意:给 \(n\) 个模式串 \(a_i\) ,对于每个模式串 \(a_i\) 有一个价值 \(p_i\) 。之后一个文本串 \(T\) ,求问最小价值。如果不能通过模式串拼接成文本串,则输出 -1。

解:

ac自动机+dp

\(f[i]=min(f[i],f[i-len[p]]+val[i])\)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=5e5+5;
 
ll f[maxn];
int val[maxn],dep[maxn];
struct AC{
    int tr[maxn][26],cnt;
    int fail[maxn];
    void insert(char s[],int len,int w)
    {
        int p=0;
        for(int i=0;i<len;i++)
        {
            if(!tr[p][s[i]-'a'])tr[p][s[i]-'a']=++cnt;
            p=tr[p][s[i]-'a'];
        }
        dep[p]=len;
        if(!val[p])val[p]=w;
        val[p]=min(val[p],w);
    }
    void getfail()
    {
        queue<int>que;
        for(int i=0;i<26;i++)if(tr[0][i])que.push(tr[0][i]);
        while(!que.empty())
        {
            int p=que.front();que.pop();
            for(int i=0;i<26;i++)
            {
                if(tr[p][i])fail[tr[p][i]]=tr[fail[p]][i],que.push(tr[p][i]);
                else tr[p][i]=tr[fail[p]][i];
            }
        }
    }
    void query(char s[],int len)
    {
        int j,p=0;
        for(int i=1;i<=len;i++)
        {
            j=p=tr[p][s[i]-'a'];
            while(j)
            {
                if(dep[j])f[i]=min(f[i],f[i-dep[j]]+val[j]);
                j=fail[j];
            }
        }
    }
}ac;
char s[maxn];
int main()
{
    int n,w,len;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%s%d",s,&w);
        ac.insert(s,strlen(s),w);
    }
    ac.getfail();
    scanf("%s",s+1);
    len=strlen(s+1);
    for(int i=1;i<=len;i++)f[i]=1e16;
    ac.query(s,len=strlen(s+1));
    printf("%lld\n",f[len]==1e16?-1:f[len]);
}
posted @ 2020-10-11 11:03  草丛怪  阅读(156)  评论(0编辑  收藏  举报