任意两个子串的 l c p lcp lcp之和等于任意两个子串的 l c s lcs lcs之和
所以我们建立 S A M SAM SAM,直接求任意两个子串的 l c s lcs lcs之和即可
在 p a r e n t parent parent树上,任意两个节点的 l c s lcs lcs就是他们的 l c a lca lca
然后下面有两种做法(其实基本一样…)
枚举每个节点作为 l c a lca lca
把原来的式子拆开得到
∑ i = 1 n ∑ j = i + 1 n ( i + j ) \sum\limits_{i=1}^n \sum\limits_{j=i+1}^n(i+j) i=1∑nj=i+1∑n(i+j)
= ∑ i = 1 n ∑ j = 1 n ( i + j ) 2 − ∑ i = 1 n i =\sum\limits_{i=1}^n\sum\limits_{j=1}^n\frac{(i+j)}{2}-\sum\limits_{i=1}^ni =i=1∑nj=1∑n2(i+j)−i=1∑ni
= ∑ i = 1 n i ∗ n − ∑ i = 1 n i =\sum\limits_{i=1}^ni*n-\sum\limits_{i=1}^ni =i=1∑ni∗n−i=1∑ni
= ( n − 1 ) ∗ ∑ i = 1 n i = ( n − 1 ) ∗ n ∗ ( n + 1 ) / 2 =(n-1)*\sum\limits_{i=1}^ni=(n-1)*n*(n+1)/2 =(n−1)∗i=1∑ni=(n−1)∗n∗(n+1)/2
那么只需要求
− 2 ∗ ∑ i = 1 n ∑ j = i + 1 n l c s ( T i , T j ) -2*\sum\limits_{i=1}^n \sum\limits_{j=i+1}^nlcs(T_i,T_j) −2∗i=1∑nj=i+1∑nlcs(Ti,Tj)
我们可以枚举作为 l c a lca lca的点进行树型 d p dp dp
两个后缀的 l c s lcs lcs就是 l c a lca lca那个点的 l o n g e s t longest longest
那么算出 f a [ p ] fa[p] fa[p]作为 l c a lca lca多少次,然后乘上 l e n [ f a [ p ] ] len[fa[p]] len[fa[p]]即可,树型 d p dp dp也就是
s i z [ p ] ∗ s i z [ f a [ p ] ] ∗ l e n [ f a [ p ] ] siz[p]*siz[fa[p]]*len[fa[p]] siz[p]∗siz[fa[p]]∗len[fa[p]]
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 2e6+10;
struct SAM
{
int zi[26],len,fa;
}sam[maxn];
char a[maxn];
int las = 1,id = 1;
int siz[maxn],c[maxn],rk[maxn],ans,n;
void insert(int c)
{
int p = las, np = ++id; las = np;
sam[np].len = sam[p].len+1; siz[np] = 1;
for( ;p&&!sam[p].zi[c];p=sam[p].fa ) sam[p].zi[c] = np;
if( !p ) { sam[np].fa = 1; return; }
int q = sam[p].zi[c];
if( sam[q].len==sam[p].len+1 ) sam[np].fa = q;
else
{
int nq = ++id;
sam[nq] = sam[q]; sam[nq].len = sam[p].len+1;
sam[np].fa = sam[q].fa = nq;
for( ;p&&sam[p].zi[c]==q;p=sam[p].fa ) sam[p].zi[c] = nq;
}
}
void solve()
{
int ans = n*(n-1)*(n+1)/2;
for(int i=1;i<=id;i++) c[sam[i].len]++;
for(int i=1;i<=id;i++) c[i] += c[i-1];
for(int i=1;i<=id;i++) rk[c[sam[i].len]--] = i;
for(int i=id;i>=1;i--)
{
int u = rk[i],f = sam[u].fa;
ans -= 2*siz[u]*siz[f]*sam[f].len;
siz[sam[u].fa] += siz[u];
}
cout << ans;
}
signed main()
{
cin >> ( a+1 ); n = strlen( a+1 );
for(int i=1;i<=n;i++) insert( a[i]-'a' );
solve();
}
计算每条边的贡献
因为我们要计算
∑ l e n ( T i ) + l e n ( T j ) − 2 ∗ l e n [ l c a ( T i , T j ) ] \sum\limits len(T_i)+len(T_j)-2*len[lca(T_i,T_j)] ∑len(Ti)+len(Tj)−2∗len[lca(Ti,Tj)]
这个式子不就是树上两点的距离和公式吗??
我们定义每条边的边权为 l e n [ u ] − l e n [ f a u ] len[u]-len[fa_u] len[u]−len[fau]即可
经过这条边的次数是 s i z [ u ] ∗ ( n − s i z [ u ] ) siz[u]*(n-siz[u]) siz[u]∗(n−siz[u])
因为有 n n n个后缀嘛!!!
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 2e6+10;
struct SAM
{
int zi[26],len,fa;
}sam[maxn];
char a[maxn];
int las = 1,id = 1;
int siz[maxn],c[maxn],rk[maxn],ans,n;
void insert(int c)
{
int p = las, np = ++id; las = np;
sam[np].len = sam[p].len+1; siz[np] = 1;
for( ;p&&!sam[p].zi[c];p=sam[p].fa ) sam[p].zi[c] = np;
if( !p ) { sam[np].fa = 1; return; }
int q = sam[p].zi[c];
if( sam[q].len==sam[p].len+1 ) sam[np].fa = q;
else
{
int nq = ++id;
sam[nq] = sam[q]; sam[nq].len = sam[p].len+1;
sam[np].fa = sam[q].fa = nq;
for( ;p&&sam[p].zi[c]==q;p=sam[p].fa ) sam[p].zi[c] = nq;
}
}
void solve()
{
int ans = 0;
for(int i=1;i<=id;i++) c[sam[i].len]++;
for(int i=1;i<=id;i++) c[i] += c[i-1];
for(int i=1;i<=id;i++) rk[c[sam[i].len]--] = i;
for(int i=id;i>=1;i--)
{
int u = rk[i];
siz[sam[u].fa] += siz[u];
ans += siz[u]*(n-siz[u])*( sam[u].len-sam[sam[u].fa].len );
}
cout << ans;
}
signed main()
{
cin >> ( a+1 ); n = strlen( a+1 );
for(int i=1;i<=n;i++) insert( a[i]-'a' );
solve();
}