BZOJ4199[NOI2015]品酒大会(后缀数组/后缀自动机+线段树)
题目链接
UOJ上有组hack数据值得一交
解析
后缀数组或后缀自动机,以下是后缀自动机我依然不会后缀数组你敢信……
容易发现把原串翻转后"\(r\)相似"就是两个前缀的最长公共后缀长度不小于\(r\)
于是想到后缀自动机
后缀自动机上一个节点\(endpos/right\)集合的大小就是满足"\(r\)相似"的位置个数,这个节点\(p\)可以更新的\(r\)满足\(maxlen_{link(p)} + 1 \le r \le maxlen_p\),可以线段树维护
于是在\(link\)树/\(parent\)树上\(dp\)求出每个节点最大值、次大值、最小值、次小值(因为权值可能为负),以及\(endpos\)集合的大小即可统计答案
注意特判\(ans1\)为零的时候\(ans2\)也为零
代码
因为改得比较多,所以有点乱
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define MAXN 300005
typedef long long LL;
const int inf = 0x3f3f3f3f;
struct SAM {
int idx, last, link[MAXN << 1], maxlen[MAXN << 1], next[MAXN << 1][26], size[MAXN << 1], deg[MAXN << 1];
int mx1[MAXN << 1], mx2[MAXN << 1], mn1[MAXN << 1], mn2[MAXN << 1];
int newnode();
int newnode(int);
void build();
int add(int);
void work();
};
struct SegmentTree {
LL maxv[MAXN << 2], add[MAXN << 2];
void build(int, int, int);
void update(int, int, int, int, int, LL, LL);
void query(int, int, int, int, LL &, LL &);
};
int N, a[MAXN];
LL ans1, ans2;
char str[MAXN];
SAM sam;
SegmentTree sgt;
int main() {
//freopen("test.in", "r", stdin);
//freopen("test.out", "w", stdout);
scanf("%d%s", &N, str);
std::reverse(str, str + N);
for (int i = N - 1; i >= 0; --i) scanf("%d", a + i);
sam.build();
sam.work();
ans1 = (LL)N * (N - 1), ans2 = std::max((LL)sam.mx1[1] * sam.mx2[1], (LL)sam.mn1[1] * sam.mn2[1]);
if (!ans1) ans2 = 0;
printf("%lld %lld\n", ans1 / 2, ans2);
for (int i = 1; i < N; ++i) {
ans1 = 0, ans2 = -0x3f3f3f3f3f3f3f3f;
sgt.query(1, 1, N, i, ans1, ans2);
if (!ans1) ans2 = 0;
printf("%lld %lld\n", ans1 / 2, ans2);
}
return 0;
}
void SAM::build() {
idx = 0, last = newnode();
for (int i = 0; i < N; ++i) {
last = add(str[i] - 'a');
mx1[last] = mn1[last] = a[i];
mx2[last] = -inf, mn2[last] = inf;
size[last] = 1;
}
for (int i = 0; i <= idx; ++i) if (!size[i]) mn1[i] = mn2[i] = inf, mx1[i] = mx2[i] = -inf;
}
int SAM::add(int c) {
int np = newnode(), p = last;
maxlen[np] = maxlen[last] + 1;
while (p && !next[p][c]) next[p][c] = np, p = link[p];
if (!p) link[np] = 1;
else {
int q = next[p][c];
if (maxlen[q] == maxlen[p] + 1) link[np] = q;
else {
int nq = newnode(q);
maxlen[nq] = maxlen[p] + 1;
link[q] = link[np] = nq;
while (p && next[p][c] == q) next[p][c] = nq, p = link[p];
}
}
return np;
}
int SAM::newnode() { return ++idx; }
int SAM::newnode(int x) {
++idx;
maxlen[idx] = maxlen[x], link[idx] = link[x];
for (int i = 0; i < 26; ++i) next[idx][i] = next[x][i];
return idx;
}
void SAM::work() {
static int q[MAXN << 1], hd, tl;
for (int i = 1; i <= idx; ++i) ++deg[link[i]];
for (int i = 1; i <= idx; ++i) if (!deg[i]) q[tl++] = i;
while (hd < tl) {
int p = q[hd++], fa = link[p];
size[fa] += size[p];
if (mx1[p] >= mx1[fa]) mx2[fa] = std::max(mx1[fa], mx2[p]), mx1[fa] = mx1[p];
else mx2[fa] = std::max(mx2[fa], mx1[p]);
if (mn1[p] <= mn1[fa]) mn2[fa] = std::min(mn1[fa], mn2[p]), mn1[fa] = mn1[p];
else mn2[fa] = std::min(mn2[fa], mn1[p]);
if (!(--deg[fa])) q[tl++] = fa;
}
sgt.build(1, 1, N);
for (int i = 2; i <= idx; ++i) {
bool t = 0;
if (mx1[i] != -inf && mx2[i] != -inf)
sgt.update(1, 1, N, maxlen[link[i]] + 1, maxlen[i], size[i], (LL)mx1[i] * mx2[i]), t = 1;
if (mn1[i] != inf && mn2[i] != inf)
sgt.update(1, 1, N, maxlen[link[i]] + 1, maxlen[i], t ? 0 : size[i], (LL)mn1[i] * mn2[i]);
}
}
void SegmentTree::update(int rt, int L, int R, int l, int r, LL sz, LL v) {
if (L >= l && R <= r) add[rt] += sz * (sz - 1), maxv[rt] = std::max(maxv[rt], v);
else {
int mid = (L + R) >> 1;
if (l <= mid) update(rt << 1, L, mid, l, r, sz, v);
if (r > mid) update(rt << 1 | 1, mid + 1, R, l, r, sz, v);
}
}
void SegmentTree::query(int rt, int L, int R, int pos, LL &res1, LL &res2) {
res1 += add[rt], res2 = std::max(res2, maxv[rt]);
if (L == R) return;
int mid = (L + R) >> 1;
if (pos <= mid) query(rt << 1, L, mid, pos, res1, res2);
else query(rt << 1 | 1, mid + 1, R, pos, res1, res2);
}
void SegmentTree::build(int rt, int L, int R) {
maxv[rt] = -0x3f3f3f3f3f3f3f3f, add[rt] = 0;
if (L == R) return;
int mid = (L + R) >> 1;
build(rt << 1, L, mid);
build(rt << 1 | 1, mid + 1, R);
}
//Rhein_E