AC通道:http://www.lydsy.com/JudgeOnline/problem.php?id=4566
【题解】
我们还是先把A串建成SAM,然后让B串在SAM上跑
因为相同子串数=不同长度的子串数*出现次数
所以每个结点对答案的贡献就是Right[x]*(len-Min(x)+1)
我们知道SAM的一个性质:Min(x)-1=Max(parent(x))
所以贡献就是Right[x]*(len-Max(parent(x)))
求出Right集即可。
因为如果当前结点被访问过一次,那么它的祖先一定被访问过了(Right(x)是Right(parent(x))的真子集)
所以我们要按照拓扑序更新parent节点
每个parent节点的贡献是Right[x]*(Max[x]-Max(parent(x)))
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<ctime>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;
#define FILE "read"
#define MAXN 400010
#define up(i,j,n) for(int i=j;i<=n;++i)
#define dn(i,j,n) for(int i=j;i>=n;--i)
#define cmax(a,b) a=max(a,b)
#define cmin(a,b) a=min(a,b)
int n,len,cnt(1),now(1),mx[MAXN],Right[MAXN],f[MAXN],c[MAXN],id[MAXN],vis[MAXN],g[MAXN],son[MAXN][27];
ll ans;
char ch[MAXN];
void insert(int x){
int p=now,np=++cnt;
mx[np]=mx[now]+1; now=np; Right[np]=1;
while(!son[p][x]&&p) son[p][x]=np,p=f[p];
if(!p) f[np]=1;
else{
int q=son[p][x];
if(mx[q]==mx[p]+1) f[np]=q;
else{
int nq=++cnt;
mx[nq]=mx[p]+1;
memcpy(son[nq],son[q],sizeof(son[q]));
f[nq]=f[q]; f[q]=f[np]=nq;
while(son[p][x]==q) son[p][x]=nq,p=f[p];
}
}
}
void topsort(){
up(i,1,cnt) c[mx[i]]++;
up(i,1,n) c[i]+=c[i-1];
up(i,1,cnt) id[c[mx[i]]--]=i;
dn(i,cnt,1) Right[f[id[i]]]+=Right[id[i]];
}
int main(){
freopen(FILE".in","r",stdin);
freopen(FILE".out","w",stdout);
scanf("%s",ch+1); n=strlen(ch+1);
up(i,1,n) insert(ch[i]-'a');
topsort();
scanf("%s",ch+1); n=strlen(ch+1); now=1;
up(i,1,n){
while(!son[now][ch[i]-'a']&&now) now=f[now],len=mx[now];
if(!son[now][ch[i]-'a']) {now=1;len=0;}
else now=son[now][ch[i]-'a'],len++;
vis[now]++; ans+=(ll)Right[now]*(len-mx[f[now]]);
}
dn(i,cnt,2) g[f[id[i]]]+=g[id[i]]+vis[id[i]];
up(i,2,cnt) ans+=(ll)g[i]*Right[i]*(mx[i]-mx[f[i]]);
printf("%lld\n",ans);
return 0;
}