【题目链接】
【思路要点】
- 建立母串的后缀树,分别考虑每一条树边上所有的字符串。
- 假设该树边指向节点\(x\),其子树内后缀结尾数为\(Size_x\),表示的字符串长度为\(Depth_i\)。
- 那么这条边上的字符串会对满足\(r\in(Depth_{father},Depth_x]\)的\(r\)的答案产生\(\binom{Size_x}{2}\)的贡献。
- DFS一遍,然后差分+前缀和即可统计第一问的答案。
- 类似地,我们需要统计在每个点的子树内选出两个不同的元素使得它们的乘积最大来解决第二问。
- 当乘积为正数,最大的乘积应当由绝对值最大的两个正数或两个负数相乘得到,否则最大的乘积应当由绝对值最小的一个正数和一个负数相乘得到。
- DFS时计算每个节点子树内元素的最大值、次大值、最小值、次小值、绝对值最小的非负数、绝对值最小的负数,即可完成询问。
- 时间复杂度\(O(N)\)。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 300005; const int MAXP = 600005; const int inf = 2e9; const long long INF = 5e18; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } struct SuffixAutomaton { struct Node { int father, depth, size, home; int Max, Nax, Min, Nin, PMin, NMax; int child[26]; } a[MAXP]; char s[MAXN]; vector <int> b[MAXP]; int n, root, size, last, val[MAXN]; long long ans[MAXN], Max[MAXN]; int newnode(int depth) { a[++size].depth = depth; return size; } void extend(int ch, int home) { int p = last, np = newnode(a[p].depth + 1); while (a[p].child[ch] == 0) { a[p].child[ch] = np; p = a[p].father; } if (a[p].child[ch] == np) a[np].father = root; else { int q = a[p].child[ch]; if (a[p].depth + 1 == a[q].depth) a[np].father = q; else { int nq = newnode(a[p].depth + 1); while (a[p].child[ch] == q) { a[p].child[ch] = nq; p = a[p].father; } memcpy(a[nq].child, a[q].child, sizeof(a[q].child)); a[nq].father = a[q].father; a[q].father = a[np].father = nq; } } a[last = np].home = home; a[last = np].size = 1; } void init() { read(n); root = size = last = 0; scanf("%s", s + 1); for (int i = 1; i <= n; i++) read(val[i]); for (int i = n; i >= 1; i--) extend(s[i] - 'a', i); for (int i = 1; i <= size; i++) b[a[i].father].push_back(i); } void ChkMax(int pos, int val) { if (val > a[pos].Max) { a[pos].Nax = a[pos].Max; a[pos].Max = val; } else chkmax(a[pos].Nax, val); } void ChkMin(int pos, int val) { if (val < a[pos].Min) { a[pos].Nin = a[pos].Min; a[pos].Min = val; } else chkmin(a[pos].Nin, val); } void work(int pos) { if (a[pos].home) { a[pos].Max = a[pos].Min = val[a[pos].home]; a[pos].Nax = -inf, a[pos].Nin = inf; a[pos].PMin = inf, a[pos].NMax = -inf; if (val[a[pos].home] >= 0) a[pos].PMin = val[a[pos].home]; else a[pos].NMax = val[a[pos].home]; } else { a[pos].Max = a[pos].Nax = -inf; a[pos].Min = a[pos].Nin = inf; a[pos].PMin = inf, a[pos].NMax = -inf; } for (unsigned i = 0; i < b[pos].size(); i++) { work(b[pos][i]); a[pos].size += a[b[pos][i]].size; ChkMax(pos, a[b[pos][i]].Max); ChkMax(pos, a[b[pos][i]].Nax); ChkMin(pos, a[b[pos][i]].Min); ChkMin(pos, a[b[pos][i]].Nin); chkmin(a[pos].PMin, a[b[pos][i]].PMin); chkmax(a[pos].NMax, a[b[pos][i]].NMax); } if (a[pos].size >= 2) { ans[a[pos].depth] += (a[pos].size - 1ll) * a[pos].size / 2; ans[a[a[pos].father].depth] -= (a[pos].size - 1ll) * a[pos].size / 2; long long tmp = 1ll * a[pos].PMin * a[pos].NMax; chkmax(tmp, 1ll * a[pos].Max * a[pos].Nax); chkmax(tmp, 1ll * a[pos].Min * a[pos].Nin); chkmax(Max[a[pos].depth], tmp); } } void calc() { for (int i = 0; i <= n - 1; i++) Max[i] = -INF; work(0); for (int i = n - 2; i >= 0; i--) { ans[i] += ans[i + 1]; chkmax(Max[i], Max[i + 1]); } ans[0] += n * (n - 1ll) / 2; for (int i = 0; i <= n - 1; i++) printf("%lld %lld\n", ans[i], Max[i] == -INF ? 0 : Max[i]); } } SAM; int main() { SAM.init(); SAM.calc(); return 0; }