多个字符串共用一个后缀自动机,便于解决多个字符串的子串间的问题。
模板题:
4566: [Haoi2016]找相同字符
Time Limit: 20 Sec Memory Limit: 256 MB
Description
给定两个字符串,求出在两个字符串中各取出一个子串使得这两个子串相同的方案数。两个方案不同当且仅当这两
个子串中有一个位置不同。
Input
两行,两个字符串s1,s2,长度分别为n1,n2。1 <=n1, n2<= 200000,字符串中只有小写字母
Output
输出一个整数表示答案
Sample Input
aabb
bbaa
Sample Output
10
做法就是把两个字符串放在一个后缀自动机中,在每个自动机节点处,可以知道这个点代表的字符串在两个串中分别出现多少次,然后统计。
建广义后缀自动机的方法就是在加入一个新的字符串时将last移至root,如果要加入字符c时,last没有关于字符c的转移那么没有区别,有的话也没有区别,因为按照常规(我的)后缀自动机写法,新建的点将没有入边,相当于不存在,只是当前的字符串的点,不是last,要另外用一个p来记录,具体看代码。
扫描二维码关注公众号,回复:
2600961 查看本文章
#include<cstdio>
#include<cstring>
#include<cctype>
#include<algorithm>
#define maxn 800005
#define Maxc 26
using namespace std;
char s[maxn];
int ch[maxn][Maxc],fail[maxn],len[maxn],last,cnt_p=1,cur,siz[2][maxn];
int c[maxn],sa[maxn];
inline void Insert(int c)
{
len[cur=cnt_p++]=len[last]+1;
int p,q;
for(p=last;p!=-1 && !ch[p][c];p=fail[p]) ch[p][c]=cur;
if(p==-1) fail[cur]=0;
else
{
q=ch[p][c];
if(len[q]==len[p]+1) fail[cur]=q;
else
{
int rec=cnt_p++;
fail[rec] = fail[q] , len[rec] = len[p]+1;
memcpy(ch[rec] , ch[q] , sizeof ch[q]);
for(;p!=-1 && q==ch[p][c];p=fail[p]) ch[p][c] = rec;
fail[q] = fail[cur] = rec;
}
}
last = cur;
}
int main()
{
scanf("%s",s);
int Len=strlen(s);
fail[0]=-1;
int p=0;
for(int i=0;i<Len;i++)
Insert(s[i]-'a') , p=ch[p][s[i]-'a'] , siz[0][p]++;
p=last=0;
scanf("%s",s);
Len=strlen(s);
for(int i=0;i<Len;i++)
Insert(s[i]-'a') , p=ch[p][s[i]-'a'] , siz[1][p]++;
for(int i=0;i<cnt_p;i++) c[i]=0;
for(int i=0;i<cnt_p;i++) c[len[i]]++;
for(int i=1;i<cnt_p;i++) c[i]+=c[i-1];
for(int i=0;i<cnt_p;i++) sa[--c[len[i]]] = i;
long long ans=0;
for(int i=cnt_p-1;i>0;i--)
{
siz[0][fail[sa[i]]] += siz[0][sa[i]] , siz[1][fail[sa[i]]] += siz[1][sa[i]];
ans+=1ll * siz[0][sa[i]] * siz[1][sa[i]] * (len[sa[i]] - len[fail[sa[i]]]);
}
printf("%lld",ans);
}