【题解】[AHOI2013]差异
\(\text{Solution:}\)
观察一下原式:
\[\sum_{i=1}^n\sum_{j=i+1}^n \text{len}(i)+\text{len}(j)-2\text{lcp}(i,j)
\]
我们发现前面那个 \(\text{len}(i)+\text{len}(j)\) 好求。主要是后面的东西:两个后缀的 \(\text{LCP}\) 怎么求。
两个后缀的最长公共前缀……这个式子又长得很像树上的东西……后缀树似乎可以做这件事。
考虑一下后缀树的性质:每一个叶子节点表示一个后缀(注意这里的叶子节点是指每一个后缀都显式地表现了出来),同时任意一个节点到根的路径都是对应一个后缀的前缀。
那么两个后缀的 \(\text{LCP}\) 不就是对应后缀树上的 \(LCA!\)
考虑一个 \(dp,\) 直接把 \(2\times LCP(i,j)\) 都算出来:设 \(f[i]\) 表示子树 \(i\) 的答案,若这个点不是后缀,那么就算出其 \(siz\) 后枚举孩子,计算 \(\sum_{v\in son[x]} siz[v]\times(siz[x]-siz[v])\times len[x]\) 就是对的。因为每一对都算了两次。
那么,如果当前点是一个,被某一个后缀所压缩掉的后缀信息,也即非显式表达出来的后缀呢?
那么注意到,上述 \(dp\) 计算中实际上只计算了每一个叶子和它求 一次 \(LCP\) 的贡献。
因为你无论枚举了哪一棵子树,这个当前的根,必然在这棵子树的外层,也就是对应每一棵子树的叶子,只会和它计算一次贡献。
所以我们需要在最后加上这一层贡献。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+10;
typedef long long ll;
namespace SAM{
int len[N],pa[N],ch[N][26],tot=1,last=1,f[N];
ll siz[N],ans;
vector<int>G[N];
void insert(const int &c){
int p=last;
int np=++tot;
last=tot;siz[np]=1;
len[np]=len[p]+1;
for(;p&&!ch[p][c];p=pa[p])ch[p][c]=np;
if(!p)pa[np]=1;
else{
int q=ch[p][c];
if(len[q]==len[p]+1)pa[np]=q;
else{
int nq=++tot;
memcpy(ch[nq],ch[q],sizeof ch[q]);
pa[nq]=pa[q];pa[q]=pa[np]=nq;
len[nq]=len[p]+1;
for(;p&&ch[p][c]==q;p=pa[p])ch[p][c]=nq;
}
}
}
void dfs(int x){
int psiz=siz[x];
for(auto i:G[x]){
dfs(i);
siz[x]+=siz[i];
}
for(auto i:G[x]){
ans+=siz[i]*(siz[x]-siz[i])*len[x];
printf("%d ::%d %d\n",x,siz[x],siz[i]);
}
printf("%lld:%lld %lld len: %lld lenfa:%lld pa:%lld\n",x,psiz,siz[x],len[x],len[pa[x]],pa[x]);
ans+=1ll*psiz*(siz[x]-psiz)*len[x];
}
void BuildTree(){
for(int i=2;i<=tot;++i)G[pa[i]].push_back(i);
dfs(1);
}
}
char s[N],t[N];
int slen;
signed main(){
scanf("%s",s+1);
slen=strlen(s+1);
for(int i=1;i<=slen;++i)t[slen-i+1]=s[i];
for(int i=1;i<=slen;++i)putchar(t[i]);
puts("");
for(int i=1;i<=slen;++i)SAM::insert(t[i]-'a');
SAM::BuildTree();
ll sum=0;
for(int i=1;i<=slen;++i)sum+=(slen-1)*(slen-i+1);
printf("%lld\n",sum-SAM::ans);
return 0;
}