题意:两个字符串取出相同子串的方案数,方案不同当且仅当这两个子串中有一个位置不同。
(SA) 后缀的前缀是子串,即求 (sum_{i<j} { m lcp}(suff_i,suff_j)),这个可以求出 (ht[]) 单调栈预处理。我们先把两个串拼起来算答案,然后再减去两个串分别计算的答案。
#include<iostream>
#include<cstdio>
#include<cstring>
#define R register int
#define ll long long
using namespace std;
namespace Luitaryi {
const int N=400010;
int n,n1,n2; ll ans;
int sa[N],rk[N],ht[N],c[N],x[N],y[N],l[N],r[N],stk[N],top;
char s1[N>>1],s2[N>>1],s[N];
inline void get_sa(char* s) {
memset(c,0,sizeof c);
memset(y,0,sizeof y);
memset(x,0,sizeof x);
R m=128;
for(R i=1;i<=n;++i) ++c[x[i]=s[i]];
for(R i=2;i<=m;++i) c[i]+=c[i-1];
for(R i=n;i;--i) sa[c[x[i]]--]=i;
for(R t=1,top=0;top<n;m=top,t<<=1) {
top=0;
for(R i=n-t+1;i<=n;++i) y[++top]=i;
for(R i=1;i<=n;++i) if(sa[i]>t) y[++top]=sa[i]-t;
memset(c,0,(m+1)<<2);
for(R i=1;i<=n;++i) ++c[x[i]];
for(R i=2;i<=m;++i) c[i]+=c[i-1];
for(R i=n;i;--i) sa[c[x[y[i]]]--]=y[i];
swap(x,y),x[sa[1]]=top=1;
for(R i=2;i<=n;++i)
x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+t]==y[sa[i-1]+t])?top:++top;
} for(R i=1;i<=n;++i) rk[sa[i]]=i;
}
inline void get_ht(char* s) {
R k=0;
for(R i=1,j;i<=n;++i) {
if(k) --k; j=sa[rk[i]-1];
while(s[j+k]==s[i+k]) ++k;
ht[rk[i]]=k;
}
}
inline ll solve() {
register ll ret=0;
stk[top=1]=1;
for(R i=2;i<=n;++i) {
while(top&&ht[stk[top]]>ht[i]) r[stk[top--]]=i;
l[i]=stk[top]; stk[++top]=i;
} while(top) r[stk[top--]]=n+1;
for(R i=1;i<=n;++i)
ret+=1ll*(r[i]-i)*(i-l[i])*ht[i];
return ret;
}
inline void main() {
scanf("%s",s1+1),n1=strlen(s1+1);
scanf("%s",s2+1),n2=strlen(s2+1);
memcpy(s+1,s1+1,n1),s[n1+1]='#';
memcpy(s+n1+2,s2+1,n2);
n=n1+n2+1,get_sa(s),get_ht(s),ans+=solve();
n=n1,get_sa(s1),get_ht(s1),ans-=solve();
n=n2,get_sa(s2),get_ht(s2),ans-=solve();
printf("%lld
",ans);
}
} signed main() {Luitaryi::main(); return 0;}
(SAM) 每个点的贡献是 ((l-{ m minlen}(now)+1) imes |{ m endpos}(now)|+sum_{pin ancestor(now)} ({ m maxlen}(p)-{ m minlen}(p)+1) imes |{ m endpos}(p)|,l为到now这个点匹配的长度)
2020.01.09