題目見此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(); }