P4248 [AHOI2013]差异

任意两个子串的 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=1nj=i+1n(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=1nj=1n2(i+j)i=1ni

= ∑ i = 1 n i ∗ n − ∑ i = 1 n i =\sum\limits_{i=1}^ni*n-\sum\limits_{i=1}^ni =i=1nini=1ni

= ( 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 =(n1)i=1ni=(n1)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) 2i=1nj=i+1nlcs(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)2len[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](nsiz[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();
}