luoguP3181 [HAOI2016]找相同字符

题意

考虑将(s1)(s2)接在一起求出相同子串个数,再求出(s1)自己匹配的相同子串个数和(s2)自己匹配的相同子串个数减去即可。

如何求相同子串个数:
我们知道子串的集合即所有后缀的前缀集合,于是实际上答案就是:
(sumlimits_{i=1}^nsumlimits_{j=i+1}^nlcp(sa_i,sa_j))

接下来就和这题相同了。

code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=400010;
int n,m,num,top;
int sa[maxn],rk[maxn],oldrk[maxn],id[maxn],tmpid[maxn],cnt[maxn],height[maxn],L[maxn],R[maxn],sta[maxn];
ll ans;
char s1[maxn],s2[maxn],s[maxn];
inline bool check(int x,int y,int k){return oldrk[x]==oldrk[y]&&oldrk[x+k]==oldrk[y+k];}
inline void get_sa(char* s,int len)
{
	memset(cnt,0,sizeof(cnt));
	num=300;
	for(int i=1;i<=len;i++)cnt[rk[i]=s[i]]++;
	for(int i=1;i<=num;i++)cnt[i]+=cnt[i-1];
	for(int i=len;i;i--)sa[cnt[rk[i]]--]=i;
	for(int t=1;t<=len;t<<=1)
	{
		int tot=0;
		for(int i=len-t+1;i<=len;i++)id[++tot]=i;
		for(int i=1;i<=len;i++)if(sa[i]>t)id[++tot]=sa[i]-t;
		tot=0;
		memset(cnt,0,sizeof(cnt));
		for(int i=1;i<=len;i++)cnt[tmpid[i]=rk[id[i]]]++;
		for(int i=1;i<=num;i++)cnt[i]+=cnt[i-1];
		for(int i=len;i;i--)sa[cnt[tmpid[i]]--]=id[i];
		memcpy(oldrk,rk,sizeof(rk));
		for(int i=1;i<=len;i++)rk[sa[i]]=check(sa[i-1],sa[i],t)?tot:++tot;
		num=tot;
		if(num==len)break;
	}
	for(int i=1,j=0;i<=len;i++)
	{
		if(j)j--;
		while(s[i+j]==s[sa[rk[i]-1]+j])j++;
		height[rk[i]]=j;
	}
}
inline ll calc(int len)
{
	ll res=0;
	sta[top=1]=1;
	for(int i=2;i<=len;i++)
	{
		while(top&&height[sta[top]]>=height[i])R[sta[top--]]=i;
		L[i]=sta[top];sta[++top]=i;
	}
	while(top)R[sta[top--]]=len+1;
	for(int i=2;i<=len;i++)res+=1ll*height[i]*(R[i]-i)*(i-L[i]);
	return res;
}
int main()
{
	scanf("%s%s",s1+1,s2+1);
	n=strlen(s1+1),m=strlen(s2+1);
	get_sa(s1,n);ans-=calc(n);
	get_sa(s2,m);ans-=calc(m);
	for(int i=1;i<=n;i++)s[i]=s1[i];
	s[n+1]='#';
	for(int i=1;i<=m;i++)s[n+i+1]=s2[i];
	get_sa(s,n+m+1);ans+=calc(n+m+1);
	printf("%lld
",ans);
	return 0;
}
原文地址:https://www.cnblogs.com/nofind/p/12052951.html