【BZOJ 3238】差异 后缀自动机+树形DP
题意
给定字符串,令$s_i$表示第$i$位开始的后缀,求$\sum_{1\le i < j \le n} len(s_i)+len(s_j)-2\times lcp(s_i,s_j)$
先考虑前面的和式,直接计算为$\frac{n(n^2-1)}{2}$,考虑后面的和式,$lcp$相关可以用sam求解,sam形成的parent树是原串的前缀树,所以两个串的最长公共后缀是在parent树上最近公共祖先对应的状态的长度$maxlen_s-maxlen_{pa_s}$,将原串反向建立sam得到后缀树,parent树上每个状态的子串个数为$Right_s$,每个状态的贡献为$2\times \binom{Right_s}{2}\times (maxlen_s-maxlen_{pa_s})$,在parent树上跑一遍dp即可求出
时间复杂度$O(n)$
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1001000;
int trans[N][30], pa[N], maxlen[N], sz, root, last, Right[N];
inline void init_sam() {
memset(trans, 0, sizeof(trans));
root = last = sz = 1;
}
inline void extend(int c, int x) {
int p = last, np = ++sz; last = np; maxlen[np] = x; Right[np] = 1;
for(; p && !trans[p][c]; p = pa[p]) trans[p][c] = np;
if(!p) {pa[np] = root; return;}
int q = trans[p][c];
if(maxlen[q] == maxlen[p] + 1) {
pa[np] = q;
}else {
int nq = ++sz;
memcpy(trans[nq], trans[q], sizeof(trans[q]));
pa[nq] = pa[q]; maxlen[nq] = maxlen[p] + 1; pa[q] = pa[np] = nq;
for(; trans[p][c] == q; p = pa[p]) trans[p][c] = nq;
}
}
inline void build(char *s) {
int len = strlen(s);
for(int i = 0; i < len; ++i) extend(s[i] - 'a', i + 1);
}
int cnt, head[N], nxt[N], to[N];
inline void init_edge() {cnt = 0; memset(head, -1, sizeof(head));}
inline void add(int u, int v) {to[cnt] = v; nxt[cnt] = head[u]; head[u] = cnt++;}
LL ans = 0;
int dfs(int u) {
for(int i = head[u]; ~i; i = nxt[i]) Right[u] += dfs(to[i]);
ans -= 1LL * Right[u] * (Right[u] - 1) * (maxlen[u] - maxlen[pa[u]]);
return Right[u];
}
inline void get() {
init_edge();
for(int i = 2; i <= sz; ++i) add(pa[i], i);
dfs(root);
}
char str[N];
int main() {
scanf("%s", str);
int len = strlen(str);
reverse(str, str + len);
init_sam();
build(str);
ans = 1LL * len * (len - 1) * (len + 1) / 2;
get();
cout << ans << endl;
return 0;
}