[AHOI2013]差异

Description

给定文本串 \(s\) ,定义 \(t_i\) 表示以第 \(i\) 个字符结尾的 \(s\) 的前缀,对下式求和。

\[\sum_{i = 1} ^ {n - 1} \sum_{j = i + 1} ^ {n} len(t_i) + len(t_j) - 2 \cdot {\rm lcp}(t_i, t_j) \]

(注: \({\rm lcp}(t_i, t_j)\) 表示两个字符串公共前缀)

\(2\leq n \leq 5 \cdot 10 ^ 5\), 字符集为所有小写字母。

Solution

有这么一个性质,大概就是 \(s_{1-i}\)\(s_{1-j}\) 的最长公共后缀为 \(longest(lca(i, j))\) (证明的话去俺博客这里),我们发现好像把整个后缀链接树倒过来就能把后缀变前缀,问题不大。

接下来是怎么把长度转换成路径,发现一个状态 \(v\) 里面的字符串数量就是 \(len(v) - len(fa)\) ,所以两条路径上对上式求和就是这个路径上所有字符串数量。

搞定,现在就是对于 \({\rm lcp}\) 求和,想到能整一个比较经典的套路,就是对单个边算贡献。发现 \(clone\) 本质上并不能算得上一个真状态,他只是从一个现有状态里面“抢”了一点在新位置多出现的字符串,但是这个位置实际上对应的是状态 \(cur\) ,所以并不能算数。

那很显然一个边的贡献就是 \(siz(v) \cdot \big(n - siz(v)\big)\) ,注意 \(clone\) 不能算,然后直接求和就完成啦。

Code

Code

/*

*/
#include 
using namespace std;
typedef long long ll;
const int N = 2e6 + 10;
int n, cnt, las, len[N], link[N], ch[N][26];
int tong[N], rk[N], siz[N]; ll ans;
char s[N];
inline void SAM(int c) {
	int cur = ++cnt, p = las;
	las = cur;
	len[cur] = len[p] + 1; siz[cur] = 1;
	while (p && !ch[p][c]) ch[p][c] = cur, p = link[p];
	if (!p) {link[cur] = 1; return ;}
	int q = ch[p][c];
	if (len[p] + 1 == len[q]) {link[cur] = q; return ;}
	int clo = ++cnt;
	link[clo] = link[q]; len[clo] = len[p] + 1;
	link[q] = link[cur] = clo;
	memcpy(ch[clo], ch[q], sizeof(ch[clo]));
	while (p && ch[p][c] == q) ch[p][c] = clo, p = link[p];
}
inline void Tong_sort() {
	for (int i = 1; i <= cnt; ++i) ++tong[len[i]];
	for (int i = 1; i <= cnt; ++i) tong[i] += tong[i - 1];
	for (int i = 1; i <= cnt; ++i) rk[tong[len[i]]--] = i;
}
int main() {
	cnt = las = 1;
	scanf("%s", s); n = strlen(s);
	for (int i = 0; i < n; ++i) SAM(s[i] - 'a');
	Tong_sort();
	for (int i = cnt, v; i >= 1; --i) {
		v = rk[i]; siz[link[v]] += siz[v];
		ans += 1ll * (len[v] - len[link[v]]) * siz[v] * (n - siz[v]);
	}
	printf("%lld\n", ans);
	return 0;
}

posted @ 2022-02-17 19:39  Illusory_dimes  阅读(20)  评论(0编辑  收藏  举报