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; }