Codeforces Round 70 E. You Are Given Some Strings...(AC自动机)

传送门

题意:给出一个文本串T,再给出N个模式串,求任意两个模式串拼接后在T中出现的次数的和(可重叠)

思路:首先暴力是不可能暴力的。可以考虑枚举两个串的结合点在T上出现的位置i,那么这个位置对答案的贡献就是以i为结尾的串的数量乘上以i+1为起始位置的串的数量。求以每个位置为起点和终点的串的数量可以用AC自动机。正向跑一遍AC自动机可以求出结尾的数量,反向跑一遍可以求出起点的数量,然后枚举结合点统计答案即可,具体看代码。

#include <bits/stdc++.h>
using namespace std;
const int maxn=2e5+5;
struct AC_automaton
{
    int tree[maxn][30];
    int fail[maxn];
    int num[maxn];
    int tot;
    int pos[maxn];
    void Insert(char *s)
    {
        int root=0;
        for(int i = 0;s[i];++i)
        {
            int tmp=s[i]-'a';
            if(!tree[root][tmp])tree[root][tmp]=++tot;
            root=tree[root][tmp];
        }
        num[root]++;
    }
    void get_fail()
    {
        queue<int>q;
        for(int i = 0;i < 26;++i)
        if(tree[0][i])q.push(tree[0][i]);
        while(!q.empty())
        {
            int now=q.front();
            q.pop();
            num[now]+=num[fail[now]];//提前加上,匹配的时候就不用跳fail了
            for(int i = 0;i < 26;++i)
            {
                if(tree[now][i])
                fail[tree[now][i]]=tree[fail[now]][i],q.push(tree[now][i]);
                else
                tree[now][i]=tree[fail[now]][i];
            }
        }
    }
    void match(char *s)
    {
        int root=0;
        for(int i = 0;s[i];++i)
        {
            root=tree[root][s[i]-'a'];
            pos[i]=num[root];
        }
    }
}AC2,AC1;
int main()
{
    char s[maxn],a[maxn];
    scanf("%s",s);
    int n;
    scanf("%d",&n);
    for(int i = 1;i <= n;++i)
    {
        scanf("%s",a);
        AC2.Insert(a);
        reverse(a,a+strlen(a));//建立反向模式串的自动机
        AC1.Insert(a);
    }
    AC1.get_fail();
    AC2.get_fail();
    AC2.match(s);
    int ns=strlen(s);
    reverse(s,s+ns);
    AC1.match(s);
    long long ans=0;
    for(int i = 0;i < ns-1;++i)
        ans+=1ll*AC2.pos[i]*AC1.pos[ns-i-2];
    printf("%lld\n",ans);
    return 0;
}

  

猜你喜欢

转载自www.cnblogs.com/aaddvvaanntteezz/p/11370622.html