「NOI2015」品酒大会

知识点: SA,并查集

原题面 Loj Luogu

「そして谁もいなくなるか?」


题意简述

给定一字符串 \(S\),位置 \(i\) 的属性值为 \(a_i\)
定义位置 \(p,q\) 为「 \(r\) 相似」,当且仅当 \(S[p:p+r-1] = S[q:q+r-1]\) 成立。
特别地,对于任意 \(1\le p,q\le n, p\not ={q}\)\(p,q\) 都是「 \(0\) 相似」的。
求:选出两个 「 \(0\sim n-1\) 相似」 的位置的 方案数,及选出两个 「 \(0\sim n-1\) 相似」的位置属性值 乘积的最大值


分析题意

SA

若两个位置 \(i,j\) 是「\(r\) 相似」的,那么它们也是「\(0\sim (r-1)\) 相似」的。
它们会对「\(0\sim r\) 相似」的答案做出贡献。
\(r\) 相似」的答案即为 「\(r\sim (n-1)\) 相似」的第一问的后缀和 与 第二问的后缀最大值。
考虑倒序枚举「\(r\) 相似」的位置并计算贡献。

\(r\) 相似」的实质即 \(\operatorname{lcp}\) 问题,先对原串跑 SA,求得 \(\operatorname{height}\)
考虑「\(r\) 相似」的定义,则对于两位置 \(i,j\),他们是 「\(\operatorname{lcp}(S[i:n],S[j:n])\) 相似」的。

引理 :LCP Theorem

\[\forall 1\le i < j\le n,\, \operatorname{lcp}(sa_i,sa_j) = \min_{k=i+1}^j\{\operatorname{height_k}\} \]

考虑按照 \(\operatorname{height}\) 将后缀排序后的后缀进行划分。
\(\operatorname{height}_i\ge r\),将 \(sa_{i-1}\)\(sa_i\) 划入一个集合,否则划入不同的集合。
划分后,对于所有大小 \(\ge 2\) 的集合,集合中后缀的 \(\operatorname{lcp}\ge r\),它们都会对 「\(r\) 相似」的答案做出贡献。
这样的所有集合的贡献累计,即为 「\(r\) 相似」的答案。

定义上述划分方式为 「\(r\) 划分」,考虑如何在此基础上获得 「\(r-1\) 划分」。
显然,只需将 \(\operatorname{height}_i = r-1\) 的后缀 \(sa_{i-1}\)\(sa_i\) 所在集合合并即可。

考虑将 \(\operatorname{height}\) 降序排序,用并查集维护集合,按上述过程依次进行合并,即可依次得到「\(n\sim 1\) 相似」的答案。

考虑如何维护集合的贡献。先考虑维护第一问:
选出「\(r\) 相似」方案数,对于「\(r\) 划分」中一个大小 \(\ge2\) 的集合,集合中任意两个后缀的 \(\operatorname{lcp} \ge r\),该集合的贡献即为 \((size-1)\times size\)
合并时直接 \(size\) 累加即可。

考虑第二问:
由于可能存在 \(a_i<0\),考虑维护集合的最大值,次大值,最小值,次小值。
为保证答案合法,四个值中的任意两个 都不能来自于 同一个位置
集合的贡献为 \(\max\{max_1\times max_2, min_1 \times min_2\}\)
合并时注意四个值的大小关系,以及集合大小。
代码中使用了 multiset 维护。

复杂度瓶颈为倍增 SA,为 \(O(n\log n)\) 级别。
使用炫酷 DC3 魔术可做到 \(O(n)\)


后缀树

两个后缀的 \(\operatorname{lcp}\),在后缀树中代表对应两叶节点的 \(\operatorname{lca}\) 的深度。

发现上述过程中,「\(r\) 划分」的合并过程,形成了树形结构。
这棵树的叶节点均为字符串的后缀。
并查集合并,实际上模拟的是后缀树的节点合并。

考虑进行树形 DP 维护上述信息。
维护子树的 \(size\) 和 子树中叶节点的最大值,次大值,最小值,次小值。
按上述规则进行合并即可。

使用 SAM 建后缀树,复杂度 \(O(n)\)


代码实现

//知识点:SA,并查集
/*
By:Luckyblock
*/
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <ctype.h>
#include <set>
#define ll long long
const int kMaxn = 3e5 + 10;
//=============================================================
char S[kMaxn];
int n, m, a[kMaxn];
int cnt[kMaxn], id[kMaxn], rkid[kMaxn];
int sa[kMaxn], rk[kMaxn << 1], oldrk[kMaxn << 1], height[kMaxn];
ll ans1[kMaxn], ans2[kMaxn];
int fa[kMaxn], size[kMaxn];
std :: multiset <int> s[kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0; char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void GetMax(ll &fir, ll sec) {
  if (sec > fir) fir = sec;
}
bool cmp(int x, int y, int w) { //判断两个子串是否相等。
  return oldrk[x] == oldrk[y] && 
         oldrk[x + w] == oldrk[y + w]; 
}
void GetHeight() {
  for (int i = 1, k = 0; i <= n; ++ i) {
    if (rk[i] == 1) k = 0;
    else {
      if (k > 0) k --;
      int j = sa[rk[i] - 1];
      while (i + k <= n && j + k <= n && 
             S[i + k] == S[j + k]) {
        ++ k;
      }
    }
    height[rk[i]] = k;
  }
}
void SuffixSort() {
  m = 300;
  for (int i = 1; i <= n; ++ i) ++ cnt[rk[i] = S[i]];
  for (int i = 1; i <= m; ++ i) cnt[i] += cnt[i - 1];
  for (int i = n; i >= 1; -- i) sa[cnt[rk[i]] --] = i;
  for (int p, w = 1; w < n; w <<= 1) {
    p = 0;
    for (int i = n; i > n - w; -- i) id[++ p] = i;
    for (int i = 1; i <= n; ++ i) {
      if (sa[i] > w) id[++ p] = sa[i] - w;
    }
    memset(cnt, 0, sizeof (cnt));
    for (int i = 1; i <= n; ++ i) ++ cnt[(rkid[i] = rk[id[i]])];
    for (int i = 1; i <= m; ++ i) cnt[i] += cnt[i - 1];
    for (int i = n; i >= 1; -- i) sa[cnt[rkid[i]] --] = id[i];
    std ::swap(rk, oldrk);
    m = 0;
    for (int i = 1; i <= n; ++ i) {
      m += (cmp(sa[i], sa[i - 1], w) ^ 1);
      rk[sa[i]] = m;
    }
  }
  GetHeight();
}
bool Compare(int fir, int sec) {
  return height[fir] > height[sec];
}
int Find(int x) {
  return fa[x] == x ? x : fa[x] = Find(fa[x]);
}
void Union(int x, int y, int z) {
  x = Find(x), y = Find(y);
  if (size[x] < size[y]) std :: swap(x, y);
  ans1[z] += 1ll * size[x] * size[y];
  for (std :: set <int> :: iterator it = s[y].begin(); it != s[y].end(); ++ it) {
    s[x].insert(* it);
  }
  int t[5];
  t[1] = *s[x].begin(), t[2] = *(++ s[x].begin());
  t[3] = *(-- s[x].end()), t[4] = *(-- (-- s[x].end()));
  GetMax(ans2[z], std :: max(1ll * t[1] * t[2], 1ll * t[3] * t[4]));
  fa[y] = x;
  size[x] += size[y];
  if (s[x].size() > 5) {
    s[x].clear();
    for (int i = 1; i <= 4; ++ i) s[x].insert(t[i]);
  }
}
//=============================================================
int main() {
  n = read();
  scanf("%s", S + 1);
  for (int i = 1; i <= n; ++ i) a[i] = read();
  SuffixSort();
  memset(ans2, - 63, sizeof (ans2));
  for (int i = 2; i <= n; ++ i) id[i] = i;
  for (int i = 1; i <= n; ++ i) fa[i] = i;
  for (int i = 1; i <= n; ++ i) size[i] = 1;
  std :: sort(id + 2, id + n + 1, Compare);
  for (int i = 1; i <= n; ++ i) s[i].insert(a[i]);
  for (int i = 2; i <= n; ++ i) {
    Union(sa[id[i] - 1], sa[id[i]], height[id[i]]);
  }
  for (int i = n - 1; i >= 0; -- i) {
    ans1[i] += ans1[i + 1];
    GetMax(ans2[i], ans2[i + 1]);
  }
  for (int i = 0; i < n; ++ i) {
    printf("%lld %lld\n", ans1[i], ans1[i] ? ans2[i] : 0); 
  }
  return 0;
}

posted @ 2020-08-18 21:35  Luckyblock  阅读(132)  评论(1编辑  收藏  举报