题目:http://acm.hdu.edu.cn/showproblem.php?pid=2222

第一道AC自动机!

T了无数边后终于知道原来它是把若干询问串建一个自动机,把模式串放在上面跑;而且只走模式串的前缀,用 fail 指针来精准遍历每个前缀的每个后缀,就能行了。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e6+5,K=27;
int T,n,c[N][K],fail[N],ed[N],len,ans,tot;
int q[N],he,tl;
char ch[N];
void init()
{
    tot=1;ans=he=tl=0;
    memset(c,0,sizeof c);memset(fail,0,sizeof fail);
    memset(ed,0,sizeof ed);
}
void insert()
{
    int nw=1;
    for(int i=1,d;i<=len;i++)
    {
        d=ch[i]-'a';
        if(!c[nw][d])
            c[nw][d]=++tot;
        nw=c[nw][d];
    }
    ed[nw]++;
}
void gtfl()
{
    for(int i=0;i<26;i++)
        if(c[1][i])
        {
            q[++tl]=c[1][i];
            fail[c[1][i]]=1;
        }
    while(he<tl)//<
    {
        int k=q[++he];
        for(int i=0;i<26;i++)
            if(c[k][i])
            {
                q[++tl]=c[k][i];
                int nw=fail[k];
                while(!c[nw][i]&&nw!=1)
                    nw=fail[nw];
                if(!c[nw][i]) fail[c[k][i]]=1;
                else fail[c[k][i]]=c[nw][i];
            }
    }
}
void solve()
{
    int nw=1;
    for(int i=1,d,cr;i<=len;i++)//只走一遍前缀
    {
        d=ch[i]-'a';
        while(!c[nw][d]&&nw!=1) nw=fail[nw];
        nw=c[nw][d]; if(!nw) nw=1;

        cr=nw;
        while(cr!=1&&ed[cr])//遍历该前缀的每个后缀
        {
            ans+=ed[cr];ed[cr]=0;cr=fail[cr];
        }
    }
}
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        init();
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            scanf("%s",ch+1); len=strlen(ch+1);
            insert();
        }
        gtfl();
        scanf("%s",ch+1); len=strlen(ch+1);
        solve();
        printf("%d\n",ans);
    }
    return 0;
}