Codeforces-1063F String Journey

Description

定义“Journey”为一个字符串序列 \(\{t_1, t_2, \cdots, t_k\}\),满足 \(\forall i\in [1, k)\)\(t_i\)\(t_{i+1}\) 的子串且 \(|t_i| < |t_{i+1}|\)\(k\) 为 Journey 的长度。

定义一个字符串 \(s\) 的“Journey”为一个字符串序列 \(\{t_1, t_2, \cdots, t_k\}\),存在一组 \(\{u_1, u_2, \cdots, u_{k+1}\}\) 满足 \(s=u_1 t_1 u_2 t_2 \cdots u_k t_k u_{k+1}\),其中 \(u_i\) 可为空。

给定一个字符串 \(s\),求它的最长 Journey。

Hint

\(|s| \le 5\times 10^5\)

Solution

一个很重要的性质:存在一个最优解,它的字符串序列长度为 \(\{k, k-1, \cdots , 2, 1\}\),即 相邻两个长度相差一。这个不难理解:对于一个其他相差超过一的解,也可以强制将长度大的子串削减,从而得到一个形如 \(\{k, k-1, \cdots , 2, 1\}\) 的解,而且显然也是最优的。那么这就提供给我们很大的便利:我们需要考虑的长度其实固定了,而且每次只会有一个字符的移除,因此只用考虑头或尾其一。

然后我们又可以通过将 \(s\) 整串翻转,使得我们考虑的长度是递增的,即 \(\{1, 2, \cdots, k-1, k\}\),也就是说本来在头尾删除字符,现在变成了添加。

接下来考虑一个 dp:\(f_i\) 表示以第 \(i\) 个位置结尾的 Journey,最后一个串的最大长度。然而这里与常规 dp 思路不同的是:我们并不考虑 \(f_i\) 如何转移而来,而是对于一个猜测的 \(f_i\) 的值,判断其可行性

由于存在 单调性,即较大的 \(f_i\) 可以,那么更小的一定可以。于是考虑二分答案 \(f_i\)。对于当前二分得到的 \(f_i=k\),需使之成立需要什么条件?根据定义可知答案的第 \(k\) 个是 \(S[i-k+1:i]\),而我们需要一个在其之前的子串 \(t\),满足 \(t=S[i-k+1:i-1]\)\(t\) 在后面加字符得到 \(S[i-k+1:i]\))或 \(t=S[i-k+2:i]\)(前面加字符)。但是,这还不够。存在这样的子串,还得要求其末尾位置 \(j\)\(f\)\(\ge k-1\)

对于子串,考虑使用 SAM 来判断。比如说一个子串 \(t\),设它是前缀 \(S[:p]\) 的一个后缀,那么我们找到这个前缀在 SAM 中的位置,然后一直跳后缀链接,直到它对应的长度区间包含了 \(|t|\),然后对于 Parent Tree 上这个结点的子树(反向跳后缀链接相当于在前面添加字符)中的所有可以表示前缀的状态,对应的 dp 值的最大值如果 \(\ge k-1\),那么说明存在一个可以转移到 \(f_i=k\) 的前驱 dp 状态。(事实上,可以表示前缀的状态必然是 Parent Tree 的叶子,因为前缀无法在前面加字符)。

跳后缀链接的过程可以用 倍增 加速,而子树求和配合 Dfs 序就可以套 线段树 了。

还有些细节:首先有 \(f_i-f_{i-1} \le 1\),那么我们可以先钦定 \(f_i=f_{i-1}+1\),然后逐步减小,这样可以去掉二分的一个 \(\log\)。同时,为了让我们找到前面的子串与当前的不相交,我们先空出一段 不更新 dp 值。然后在逐步减小是慢慢更新过来。

时间复杂度 \(O(n\log n)\)

Code

/*
 * Author : _Wallace_
 * Source : https://www.cnblogs.com/-Wallace-/
 * Problem :  Codeforces 1063F String Journey
 */
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <vector>

const int N = 1e6 + 5;
const int logN = 23;
const int S = 26;

int ch[N][S], link[N], len[N];
int total = 1, last = 1;
int extend(int c) {
  int p = last, np = last = ++total;
  len[np] = len[p] + 1;
  for (; p && !ch[p][c]; p = link[p]) ch[p][c] = np;
  if (!p) { link[np] = 1; return np; }
  int q = ch[p][c];
  if (len[q] == len[p] + 1) { link[np] = q; return np; }
  int nq = ++total;
  memcpy(ch[nq], ch[q], S << 2), link[nq] = link[q];
  len[nq] = len[p] + 1, link[np] = link[q] = nq;
  for (; p && ch[p][c] == q; p = link[p]) ch[p][c] = nq;
  return np;
}

std::vector<int> adj[N];
int fa[N][logN], indfn[N], oudfn[N], timer(0);
void dfs(int x) {
  indfn[x] = timer, fa[x][0] = link[x];
  for (int j = 1; j < logN; j++) fa[x][j] = fa[fa[x][j - 1]][j - 1];
  if (adj[x].empty()) { oudfn[x] = ++timer; return; }
  for (auto y : adj[x]) dfs(y);
  oudfn[x] = timer;
}

namespace segt {
#define mid ((l + r) >> 1)
  int val[N << 2];
  void update(int x, int l, int r, int p, int v) {
    if (l == r) { val[x] = v; return; }
    if (p <= mid) update(x << 1, l, mid, p, v);
    else update(x << 1 | 1, mid + 1, r, p, v);
    val[x] = std::max(val[x << 1], val[x << 1 | 1]);
  }
  int query(int x, int l, int r, int ql, int qr) {
    if (ql <= l && r <= qr) return val[x];
    if (ql > r || l > qr) return 0;
    return std::max(query(x << 1, l, mid, ql, qr), query(x << 1 | 1, mid + 1, r, ql, qr));
  }
#undef mid
}

int n, loc[N], f[N];
char s[N];

int upto(int x, int tar_l) {
  for (int j = logN - 1; ~j; j--)
    if (fa[x][j] && len[fa[x][j]] >= tar_l) x = fa[x][j];
  return x;
}

signed main() {
  scanf("%d%s", &n, s + 1);
  std::reverse(s + 1, s + 1 + n);
  for (int i = 1; i <= n; i++) loc[i] = extend(s[i] - 'a');
  for (int i = 2; i <= total; i++) adj[link[i]].push_back(i);
  dfs(1);

  for (int i = 1, j = 0; i <= n; i++) {
    f[i] = f[i - 1] + 1;
    while (true) {
      int x = upto(loc[i], f[i] - 1), y = upto(loc[i - 1], f[i] - 1);
      if (segt::query(1, 1, n, indfn[x] + 1, oudfn[x]) >= f[i] - 1) break;
      if (segt::query(1, 1, n, indfn[y] + 1, oudfn[y]) >= f[i] - 1) break;
      --f[i], ++j, segt::update(1, 1, n, oudfn[loc[j]], f[j]);
    }
  }

  printf("%d\n", *std::max_element(f + 1, f + 1 + n));
  return 0;
}
posted @ 2020-12-14 22:28  -Wallace-  阅读(246)  评论(0编辑  收藏  举报