题目见此c++
题解:首先全部后缀都在最后一个np节点,而后他们都是从1号点出发沿一些字符边到达这个点的,因此下文称1号点为根节点,咱们思考一下何时会产生lcp,显然是当他们从根节点开始一直跳相同节点的时候,因此思路就是先找出每一个节点被几个后缀通过,这显然把边反转倒着找就能够了,而后他会被出现次数sz个串通过。spa
出现次数等于parent树子树中np类节点的个数,这跑个dfs就行了,一个相同前缀产生的贡献是sz*(sz-1)/2code
而后思考一个点可能表明多个子串,可是他们的出现次数都是相同的,因此单个点的贡献为上面的单个贡献再乘上一个有几个子串blog
子串的个数为parent树父亲节点的最大长度减去该节点的最大长度get
这样子在从根开始dfs,若是通过某个点只有一个后缀通过,就说明lcp结束了,就不用再搜该点了。it
上面就求出了lcp的和ast
至于前面那个式子,只须要打个表找个规律发现是(n-1)*n*(n+1)/2就能够了class
虽然常数大点可是仍是后缀自动机复杂度的di
但其实不用这么复杂,只要翻过来就能够建出原串后缀树,lcp就是后缀树的两个节点的lca,跑个树形dp就能够了。思考
代码由于没用链式前向星存边因此不开o2会t,但仍是贴一下吧
#include<bits/stdc++.h> #define N 1000010 using namespace std; int n; int gg=0; struct SAM { struct point { int son[26],fa,len,mx; }t[N]; int cnt=1,last=1; int f[N],sz[N]; bool vis[N]; vector<int> g[N],e[N]; long long lcp=0ll; void add(int c) { int p=last; int np=++cnt; t[np].len=t[p].len+1; sz[np]=1; while(p&&(!t[p].son[c])) { t[p].son[c]=np; p=t[p].fa; } if(!p) t[np].fa=1; else { int q=t[p].son[c],nq; if(t[p].len+1==t[q].len) { t[np].fa=q; } else { nq=++cnt; t[nq]=t[q]; t[nq].len=t[p].len+1; t[q].fa=t[np].fa=nq; while(p&&(t[p].son[c]==q)) { t[p].son[c]=nq; p=t[p].fa; } } } last=np; } void dfs(int now) { t[now].mx=t[now].len-t[t[now].fa].len; for(int i=0;i<26;i++) { if(t[now].son[i]) e[t[now].son[i]].push_back(now); } for(int i=0;i<g[now].size();i++) { dfs(g[now][i]); sz[now]+=sz[g[now][i]]; } } void dfs1(int now) { vis[now]=1; for(int i=0;i<e[now].size();i++) { f[e[now][i]]++; if(!vis[e[now][i]]) { dfs1(e[now][i]); } } } void dfs3(int now) { vis[now]=1; if(f[now]) lcp+=t[now].mx*(1ll*sz[now]*(sz[now]-1)/2); for(int i=0;i<26;i++) { if(f[t[now].son[i]]&&sz[t[now].son[i]]>1&&(!vis[t[now].son[i]])) { dfs3(t[now].son[i]); } } } void solve() { for(int i=1;i<=cnt;i++) g[t[i].fa].push_back(i); dfs(1); sz[1]=0; memset(vis,0,sizeof(vis)); dfs1(last); memset(vis,0,sizeof(vis)); dfs3(1); long long len=1ll*n*(n-1)*(n+1)/2; printf("%lld\n",len-2*lcp); } }sam; char s[500050]; int main() { scanf("%s",s); n=strlen(s); for(int i=0;i<n;i++) { sam.add(s[i]-'a'); } sam.solve(); }