广义后缀自动机

多个字符串共用一个后缀自动机,便于解决多个字符串的子串间的问题。

模板题:

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

猜你喜欢

转载自blog.csdn.net/qq_35950004/article/details/81302701