[BZOJ 3238] [AHOI 2013] 差异 【后缀数组 + 单调栈】
题目链接:BZOJ - 3238
题目分析
显然,这道题就是求任意两个后缀之间的LCP的和,这与后缀数组的联系十分明显。
求出后缀数组后,求出字典序相邻两个后缀的LCP,即 Height 数组。
那么我们可以用这个 Height 数组求出所有后缀之间 LCP 的和。
我们用 f[i] 表示字典序第 i 的后缀与字典序在 i 之后的所有后缀的 LCP 的和。
我们知道,两个后缀的 LCP 为 Height 数组中这两个后缀之间的最小值。
我们从最后向前推 i ,用一个单调栈维护后面的 Height 单调不上升,然后用 St[Top] 来推 f[i] 即可,具体见代码。
代码
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <cmath> #include <algorithm> using namespace std; const int MaxL = 500000 + 5; typedef long long LL; LL Ans, Temp; LL f[MaxL]; int n, Top; int A[MaxL], Rank[MaxL], SA[MaxL], Height[MaxL], St[MaxL]; int VA[MaxL], VB[MaxL], VC[MaxL], Sum[MaxL]; char S[MaxL]; inline bool Cmp(int *a, int x, int y, int l) { return (a[x] == a[y]) && (a[x + l] == a[y + l]); } void DA(int *A, int n, int m) { int *x, *y, *t; x = VA; y = VB; for (int i = 1; i <= m; ++i) Sum[i] = 0; for (int i = 1; i <= n; ++i) ++Sum[x[i] = A[i]]; for (int i = 2; i <= m; ++i) Sum[i] += Sum[i - 1]; for (int i = n; i >= 1; --i) SA[Sum[x[i]]--] = i; int p, q; p = 0; for (int j = 1; p < n; j <<= 1, m = p) { q = 0; for (int i = n - j + 1; i <= n; ++i) y[++q] = i; for (int i = 1; i <= n; ++i) { if (SA[i] <= j) continue; y[++q] = SA[i] - j; } for (int i = 1; i <= m; ++i) Sum[i] = 0; for (int i = 1; i <= n; ++i) VC[i] = x[y[i]]; for (int i = 1; i <= n; ++i) ++Sum[VC[i]]; for (int i = 2; i <= m; ++i) Sum[i] += Sum[i - 1]; for (int i = n; i >= 1; --i) SA[Sum[VC[i]]--] = y[i]; t = x; x = y; y = t; p = 1; x[SA[1]] = 1; for (int i = 2; i <= n; ++i) x[SA[i]] = Cmp(y, SA[i], SA[i - 1], j) ? p : ++p; } for (int i = 1; i <= n; ++i) Rank[SA[i]] = i; //GetHeight int h = 0, o; for (int i = 1; i <= n; ++i) { if (Rank[i] == 1) continue; o = SA[Rank[i] - 1]; while (A[i + h] == A[o + h]) ++h; Height[Rank[i]] = h; if (h > 0) --h; } } int main() { scanf("%s", S + 1); n = strlen(S + 1); for (int i = 1; i <= n; ++i) A[i] = S[i] - 'a' + 1; DA(A, n, 26); Ans = 0ll; Temp = 0ll; for (int i = 1; i <= n; ++i) Ans += (LL)(n - i + 1) * (LL)(n - 1); Top = 0; St[++Top] = n + 1; for (int i = n; i >= 2; --i) { while (Top > 0 && Height[St[Top]] > Height[i]) --Top; int x = St[Top]; f[i] = (LL)Height[i] + (LL)Height[i] * (x - i - 1) + (LL)f[x]; Temp += f[i]; St[++Top] = i; } Ans -= Temp * 2ll; printf("%lld\n", Ans); return 0; }