题目链接:差异
写题时发现这道题当初已经用后缀数组写过了……但是既然学了后缀自动机那就再写一遍吧……
观察一下题目所给的式子:[sum_{1leqslant i < j leqslant n}len(T_i)+len(T_j)-2lcp(T_i,T_j)]
可以发现前面两项其实是可以在(O(n))的时间内算出来的。于是我们就只需要考虑后面那一项怎么算。
题目要求的是所有后缀的最长公共前缀之和,那么把串反一下就变成了所有前缀的最长公共后缀之和。
那么,我们构出翻转后的串的(parent)树(其实就是原串的后缀树),两个串的公共后缀就是他们在(parent)树上的(lca)节点包含的最长子串长度。
然后我们就可以枚举每个节点作为(lca),然后统计一下它的两两子树(right)集合大小之积即可。
下面贴代码:
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout) #define maxn 1000010 using namespace std; typedef long long llg; char s[maxn>>1]; int n,tot,la,head[maxn],next[maxn],to[maxn],tt; int son[maxn][26],len[maxn],fa[maxn],siz[maxn]; llg ans; void link(int x,int y){to[++tt]=y;next[tt]=head[x];head[x]=tt;} void insert(int p,int x){ int np=++tot; siz[np]=1; len[np]=len[p]+1; for(;p && !son[p][x];p=fa[p]) son[p][x]=np; if(!p) fa[np]=1; else{ int q=son[p][x]; if(len[q]==len[p]+1) fa[np]=q; else{ int nq=++tot; memcpy(son[nq],son[q],sizeof(son[q])); fa[nq]=fa[q]; fa[np]=fa[q]=nq; len[nq]=len[p]+1; for(;son[p][x]==q;p=fa[p]) son[p][x]=nq; } } la=np; } void dfs(int u){ for(int i=head[u],v;v=to[i],i;i=next[i]){ dfs(v); ans-=(llg)siz[u]*siz[v]*len[u],siz[u]+=siz[v]; } } int main(){ File("a"); scanf("%s",s+1); n=strlen(s+1); tot=la=1; for(int i=n>>1;i;i--) swap(s[i],s[n-i+1]); for(int i=1;i<=n;i++) insert(la,s[i]-'a'); for(int i=2;i<=tot;i++) link(fa[i],i); dfs(1); ans<<=1; ans+=((llg)(n+1)*n*(n-1))>>1; printf("%lld ",ans); return 0; }