[HAOI2016]找相同字符 广义后缀自动机_统计出现次数
题目描述:
给定两个字符串,求出在两个字符串中各取出一个子串使得这两个子串相同的方案数。两个方案不同当且仅当这两个子串中有一个位置不同。
输入输出格式
输入格式:
两行,两个字符串 s1,s2,长度分别为n1,n2。1 <=n1, n2<= 200000,字符串中只有小写字母
输出格式:
输出一个整数表示答案
题解:
对 $2$ 个字符串建立一个广义后缀自动机.
实际上,广义后缀自动机就是对多个字符串用一个自动机加以维护.
每加入完毕一个字符串时,将 $last$ 设为 $1$.
插入字符时,若 $ch[last][c]==0$,则与普通的插入无区别.
若 $ch[last][c]!=0$ ,我们就将这个情况考虑成普通插入中 $last$ 的祖先中有 $ch[q][c]!=0$ 的情况即可.
对每一种字符串都维护一个 $cnt$ 数组即可.
上述讲的是广义后缀自动机的建立.
在后缀自动机上,由于每个节点的 $right$ 的区间刚好是 $[right[f[p]],right[p]]$,点和点之间的计算时互不矛盾的.
每个点的贡献为: $(dis[p]-dis[f[p]])*cnt[p][0]*cnt[p][1]$.
Code:
#include <cstdio> #include <algorithm> #include <cstring> #define setIO(s) freopen(s".in","r",stdin) #define maxn 1000000 #define N 30 #define ll long long using namespace std; char str[maxn]; int last=1,tot=1,n,m; int ch[maxn][N],cnt[maxn][2],f[maxn],dis[maxn],rk[maxn]; long long C[maxn],ans; void ins(int c){ int np=++tot,p=last; last=np; if(ch[p][c]){ int q=ch[p][c]; if(dis[q]==dis[p]+1) last=q; else { int nq=++tot; last=nq; f[nq]=f[q],dis[nq]=dis[p]+1; memcpy(ch[nq],ch[q],sizeof(ch[q])); f[q]=nq; while(p&&ch[p][c]==q) ch[p][c]=nq,p=f[p]; } } else{ dis[np]=dis[p]+1; while(p&&!ch[p][c]) ch[p][c]=np,p=f[p]; if(!p) f[np]=1; else{ int q=ch[p][c],nq; if(dis[q]==dis[p]+1) f[np]=q; else{ nq=++tot; dis[nq]=dis[p]+1; memcpy(ch[nq],ch[q],sizeof(ch[q])); f[nq]=f[q],f[q]=f[np]=nq; while(p&&ch[p][c]==q) ch[p][c]=nq,p=f[p]; } } } } int main(){ //setIO("input"); scanf("%s",str),n=strlen(str); for(int i=0;i<n;++i) ins(str[i]-'a'),cnt[last][0]=1; scanf("%s",str),m=strlen(str),last=1; for(int i=0;i<m;++i) ins(str[i]-'a'),cnt[last][1]=1; for(int i=1;i<=tot;++i) ++C[dis[i]]; for(int i=1;i<=tot;++i) C[i]+=C[i-1]; for(int i=1;i<=tot;++i) rk[C[dis[i]]--]=i; for(int i=tot;i>=1;--i) { int p=rk[i]; cnt[f[p]][0]+=cnt[p][0],cnt[f[p]][1]+=cnt[p][1]; ans+=(ll)(dis[p]-dis[f[p]])*cnt[p][0]*cnt[p][1]; } printf("%lld",ans); return 0; }