题意

给一个长度为 $n$ 的字符串,求

其中,$\text{lcp}$ 表示最长公共前缀。

题解

后缀的最长公共前缀不太好求,我们把给的字符串翻转,再将原式变一下,就成了

其中,$\operatorname{lcs}$ 表示最长公共后缀。

前一部分很好求,即 $n\times (n+1)\times (n-1)/2$。

后一部分可以对原串建 $SAM$,对于 $SAM$ 上的一个结点,它的祖先都是它的后缀,所以求最长公共后缀即在 $parent\ \ tree$ 上求这两个结点的 $LCA$。

但是 $n$ 太大,不能直接枚举 $i$ 和 $j$,考虑对每个 $parent\ \ tree$ 上的结点求该结点是多少个点对的 $LCA$,具体做法为每遍历一个儿子,就把该儿子的子树大小乘上前面已经遍历过的子树大小,再乘上 $longest$ 的二倍累加到答案中。时间复杂度为 $O(n)$。

其实题面上也有提示,原式很像树上求两点之间的路径长度,于是可以联想到 $parent\ \ tree$ 上求 $LCA$。
(谁会联想这个啊)

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mn=2e6+7;
struct node {
    int ch[26],fa;
    ll len;
}sam[mn];
int last=1,tot=1;
char tmp[mn],s[mn];
int fr[mn],nx[mn],to[mn],tt=0;
ll ans=0,cnt[mn];
void add(int x)
{
    int p=last,now=last=++tot;cnt[tot]=1;
    sam[now].len=sam[p].len+1;
    for(;p&&!sam[p].ch[x];p=sam[p].fa)  sam[p].ch[x]=now;
    if(!p)  sam[now].fa=1;
    else
    {
        int son1=sam[p].ch[x];
        if(sam[son1].len==sam[p].len+1)  sam[now].fa=son1;
        else
        {
            int son2=++tot;
            sam[son2]=sam[son1];
            sam[son2].len=sam[p].len+1;
            sam[son1].fa=sam[now].fa=son2;
            for(;p&&sam[p].ch[x]==son1;p=sam[p].fa)  sam[p].ch[x]=son2;
        }
    }
}
void link(int x,int y)
{
    ++tt;
    nx[tt]=fr[x];
    fr[x]=tt;
    to[tt]=y;
}
void dfs(int x)
{
    ll sum=0;
    for(int i=fr[x];i;i=nx[i])
    {
        int y=to[i];
        dfs(y);
        sum+=sam[x].len*cnt[x]*cnt[y]*2;  //这里不开 long long 会乘炸
        cnt[x]+=cnt[y];
    }
    ans-=sum;
}
int main()
{
    scanf("%s",tmp+1);
    int n=strlen(tmp+1);
    for(int i=1;i<=n;++i)
      s[n-i+1]=tmp[i];
    for(int i=1;i<=n;++i)  add(s[i]-'a');
    for(int i=2;i<=tot;++i)  link(sam[i].fa,i);
    ans=(ll)n*(n+1)*(n-1)/2;  //这里也会乘炸
    dfs(1);
    cout<<ans;
    return 0;
}

菜就多练练