题意:
给你一个字符串,
T
i
T_i
Ti表示第
i
i
i个字符开始的后缀,求
∑
1
<
=
i
<
j
<
=
n
l
e
n
(
T
i
)
+
l
e
n
(
T
j
)
−
2
∗
l
c
p
(
T
i
,
T
j
)
\sum_{1<=i<j<=n}len(T_i)+len(T_j)-2*lcp(T_i,T_j)
∑1<=i<j<=nlen(Ti)+len(Tj)−2∗lcp(Ti,Tj),其中
l
e
n
(
i
)
len(i)
len(i)表示字符串
i
i
i的长度,
l
c
p
(
i
,
j
)
lcp(i,j)
lcp(i,j)表示字符串
i
i
i和
j
j
j的最长公共前缀。n<=500000。
题解:
首先我们可以化一下那个式子,有一个直觉是那个与字符串长度有关的部分可以直接算出来,那么我们先不管减lcp那一部分,会发现可以转化成
∑
1
<
=
i
<
j
<
=
n
l
e
n
(
T
i
)
+
l
e
n
(
T
j
)
=
∑
i
=
1
n
−
1
∑
j
=
i
+
1
i
+
j
=
(
n
−
1
)
∗
n
∗
(
n
+
1
)
/
2
\sum_{1<=i<j<=n}len(T_i)+len(T_j)=\sum_{i=1}^{n-1}\sum_{j=i+1}i+j=(n-1)*n*(n+1)/2
∑1<=i<j<=nlen(Ti)+len(Tj)=∑i=1n−1∑j=i+1i+j=(n−1)∗n∗(n+1)/2。那么我们就可以算出前面一部分,只要想办法减去lcp就可以了。后缀的LCP是比较容易用SAM算的,我们考虑建出SAM,然后我们发现,其实两个后缀的LCP长度就是parent树上根到LCA的字符串长度,那么我们就可以建出parent树,然后在树上进行树形dp,求出每个点为lca时对答案的贡献,其中字符串长度在建SAM时已经处理好了,所以在树形dp的同时再维护一下子树的size就好了。注意答案会爆int。
update:
再看时意识到,前缀的最长公共后缀之和与后缀的最长公共前缀之和是相同的,我这里写的是前缀的最长公共后缀,于是并没有把串翻转
代码:
#include <bits/stdc++.h>
using namespace std;
int n,fa[2000010],len[2000010],lst=1,rt=1,cnt=1,ch[2000010][26];
int sz[2000010],hed[2000010],num;
char s[500010];
long long ans;
struct node
{
int to,next;
}a[4000010];
inline void insert(int x)
{
int cur=++cnt,pre=lst;
lst=cur;
len[cur]=len[pre]+1;
sz[cur]=1;
for(;pre&&!ch[pre][x];pre=fa[pre])
ch[pre][x]=cur;
if(!pre)
fa[cur]=rt;
else
{
int ji=ch[pre][x];
if(len[ji]==len[pre]+1)
fa[cur]=ji;
else
{
int gg=++cnt;
memcpy(ch[gg],ch[ji],sizeof(ch[ji]));
len[gg]=len[pre]+1;
fa[gg]=fa[ji];
fa[ji]=fa[cur]=gg;
for(;pre&&ch[pre][x]==ji;pre=fa[pre])
ch[pre][x]=gg;
}
}
}
inline void add(int from,int to)
{
a[++num].to=to;
a[num].next=hed[from];
hed[from]=num;
}
inline void dfs(int x)
{
for(int i=hed[x];i;i=a[i].next)
{
int y=a[i].to;
dfs(y);
ans-=2ll*len[x]*sz[x]*sz[y];
sz[x]+=sz[y];
}
}
int main()
{
scanf("%s",s+1);
n=strlen(s+1);
for(int i=1;i<=n;++i)
insert(s[i]-'a');
for(int i=2;i<=cnt;++i)
add(fa[i],i);
dfs(1);
ans+=1ll*n*(n-1)/2*(n+1);
printf("%lld\n",ans);
return 0;
}