题目大意: 给出一个字符串,求出这个柿子的值: ∑ 1 ≤ i < j ≤ n l e n ( T i ) + l e n ( T j ) − 2 × l c p ( T i , T j ) \sum\limits_{1\leq i<j\leq n} len(T_i)+len(T_j)-2\times lcp(T_i,T_j) 1≤i<j≤n∑len(Ti)+len(Tj)−2×lcp(Ti,Tj)。
题解
显然 l e n ( T i ) + l e n ( T j ) len(T_i)+len(T_j) len(Ti)+len(Tj) 的和可以 O ( 1 ) O(1) O(1) 求出来,就是 ( n − 1 ) × n × ( n + 1 ) 2 \frac {(n-1)\times n\times (n+1)} 2 2(n−1)×n×(n+1)。
然后考虑后面如何求出任意两个后缀的最长公共前缀之和,把串反过来,就变成了求任意两个前缀的最长公共后缀。
建出SAM,考虑每一个状态的贡献,假设一个状态的 e n d p o s endpos endpos 集大小为 s i z e [ i ] size[i] size[i],就说明其中任意一个子串都恰好出现过 s i z e [ i ] size[i] size[i] 次,对于任意一个子串,假设他在 [ a , b ] [a,b] [a,b] 和 [ c , d ] [c,d] [c,d] 出现过,那么以 b b b 结尾和以 d d d 结尾的前缀的最长公共后缀至少会延伸到 a , c a,c a,c 这两个位置,所以可以提供 1 1 1 的贡献,至于在 a + 1 a+1 a+1 和 c + 1 c+1 c+1 位置的匹配,自然会有 [ a + 1 , b ] [a+1,b] [a+1,b] 和 [ c + 1 , d ] [c+1,d] [c+1,d] 两个子串来提供贡献 ,一共有 s i z e [ i ] × ( s i z e [ i ] − 1 ) size[i]\times(size[i]-1) size[i]×(size[i]−1) 种这样的情况,所以贡献就是 s i z e [ i ] × ( s i z e [ i − 1 ] ) size[i]\times (size[i-1]) size[i]×(size[i−1])。
而一个状态内有 l e n ( i ) − l e n ( l i n k ( i ) ) len(i)-len(link(i)) len(i)−len(link(i)) 个子串,所以贡献再乘上这个数量即可。
代码如下:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 1000010
#define ll long long
int n;
char s[maxn];
struct state{
int len,link,next[26];
}st[maxn];
int id=0,last=0,now,p,q;
int size[maxn];
void extend(int x)
{
now=++id;
st[now].len=st[last].len+1;size[now]=1;
for(p=last;p!=-1&&!st[p].next[x];p=st[p].link)st[p].next[x]=now;
if(p!=-1)
{
q=st[p].next[x];
if(st[p].len+1==st[q].len)st[now].link=q;
else
{
int clone=++id;
st[clone]=st[q];st[clone].len=st[p].len+1;
for(;p!=-1&&st[p].next[x]==q;p=st[p].link)st[p].next[x]=clone;
st[q].link=st[now].link=clone;
}
}
last=now;
}
int c[maxn],A[maxn];
void get_size()
{
for(int i=1;i<=id;i++)c[st[i].len]++;
for(int i=1;i<=id;i++)c[i]+=c[i-1];
for(int i=1;i<=id;i++)A[c[st[i].len]--]=i;
for(int i=id;i>=1;i--)size[st[A[i]].link]+=size[A[i]];
}
int main()
{
scanf("%s",s+1);n=strlen(s+1);st[0].link=-1;
for(int i=n;i>=1;i--)extend(s[i]-'a');
get_size(); ll ans=1ll*(n-1)*n*(n+1)/2ll;
for(int i=1;i<=id;i++)ans-=1ll*(size[i]-1)*size[i]*(st[i].len-st[st[i].link].len);
printf("%lld",ans);
}