「TJOI / HEOI2016」字符串

知识点: 二分答案,SA + 主席树,SAM + 线段树合并

原题面 Loj Luogu

简述

给定一长度为 \(n\) 的字符串,\(m\) 个询问。
每次询问给定参数 \(a,b,c,d\),求子串 \(S[a:b]\)所有子串,与子串 \(S[c:d]\) 的最长公共前缀的最大值。
\(1\le n,m\le 10^5, 1\le a\le b\le n, 1\le c\le d\le n\)

分析

对于每一个询问,答案满足单调性,考虑二分答案。
\(l\) 为当前二分到的最长的,子串 \(S[a:b]\)所有子串,与子串 \(S[c:d]\) 的最长公共前缀。
检查 \(l\) 是否合法,有两种方法:

线段树合并

\(l\) 合法,则 \(S[c:c+l-1]\)\(S[a:b]\) 中出现过。
则其结束位置位于 \(S[a+l-1:b]\) 中。
考虑对原串建 SAM,跑出子串 \(S[c:c+l-1]\) 的状态 \(u\),检查状态 \(u\) 是否维护了 \(S[a:b]\) 的信息。
具体地,检查该状态的子树中的状态 \(v\),是否 \(\exist x\in [a, b-l+1], x\in\operatorname{endpos}(v)\)

这样为什么是对的?
子树中的状态 \(v\),其 \(\operatorname{len}(v)>\operatorname{len}(u)\ge l\),且均以状态 \(u\) 代表的子串,即 \(S[c:c+l-1]\) 作为后缀。
\(\exist x\in [a+l-1, b], x\in\operatorname{endpos}(v)\),则存在一个子串 \(S[?:x]\),其长度 \(\ge l\),且以 \(S[c:c+l-1]\) 为后缀。
显然,\(S[a:x]\) 也以 \(S[c:c+l-1]\) 为后缀,证明了 \(l\) 的合法性。

维护 \(\operatorname{endpos}\) 的信息可对每个状态都维护一棵权值线段树。
线段树合并自底向上更新信息即可。


然后 T 了。暴力跑 \(S[c:c+l-1]\) 的状态太慢啦!
考虑套路,在建 SAM 的时候记录每一个前缀 \(S[1:x]\) 的对应状态。
二分时先找到 \(S[1:c+l-1]\) 对应状态 \(u\)
发现 \(S[c:c+l-1]\) 为该前缀的一个后缀,对应状态是 \(u\) parent 树上的祖先。
倍增上跳到最后一个 \(\operatorname{len} \le l\) 的位置,即为 \(S[c:c+l-1]\) 对应状态。

先进行线段树合并预处理 \(\operatorname{endpos}\) 信息,查询时二分套线段树查询,复杂度 \(O(n\log n +n\log^2 n)\) 级别。

SA

分析可知,若 \(l\) 合法,那么会存在至少一个后缀 \(x\) 满足:

  • 开头在 \([a:b-l+1]\) 中。
  • \(\operatorname{lcp}(x, c)\ge l\)

对于第二个限制,考虑 \(\operatorname{lcp}\) 的单调性。\(\operatorname{lcp}(x,c)\) 是一个在 \(c\) 处取极值的单峰函数。
则满足条件的 \(x\) 的取值,一定是 \(sa\) 数组上连续的一段。
套路地对 \(\operatorname{height}\) 建立 st 表,即可 \(O(1)\) 查询 \(\operatorname{lcp}\),于是可以通过二分排名快速得到后缀 \(x\) 排名的取值范围,将限制二也转化为了区间限制形式。

限制一限制了后缀的区间,限制二限制了 \(rk\) 的区间。查询这样的后缀的存在性,变成了一个静态二维数点问题。
\(sa\) 数组建立主席树维护即可。

注意保证查询区间 \(l\le r\)

实现

SA

//知识点:SA,二分答案,主席树
/*
By:Luckyblock
*/
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <ctype.h>
#define ll long long
const int kMaxn = 1e5 + 10;
//=============================================================
char S[kMaxn];
int n, m, ans, cnt[kMaxn], id[kMaxn], rkid[kMaxn];
int sa[kMaxn], rk[kMaxn << 1], oldrk[kMaxn << 1], height[kMaxn];
int MaxHeight[kMaxn][20], Log2[kMaxn];;
int lson[kMaxn << 5], rson[kMaxn << 5], size[kMaxn << 5];
int node_num, root[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(int &fir, int 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;
  }
}
int QueryLcp(int l_, int r_) {
  int k = Log2[r_ - l_ + 1];
  return std :: min(MaxHeight[l_][k], MaxHeight[r_ - (1 << k) + 1][k]);
}
void MakeSt() {
  for (int i = 2; i <= n; ++ i) MaxHeight[i][0] = height[i];
  for (int i = 2; i <= n; ++ i) {
    Log2[i] = Log2[i >> 1] + 1;
  }
  for (int j = 1; j < 20; ++ j) {
    for (int i = 1; i + (1 << j) - 1 <= n; ++ i) {
      MaxHeight[i][j] = std :: min(MaxHeight[i][j - 1], 
                                   MaxHeight[i + (1 << (j - 1))][j - 1]);
    }
  }
}
void SuffixSort() {
  int 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();
  MakeSt();
}
void Insert(int pre_, int &now_, int L_, int R_, int val_) {
  now_ = ++ node_num;
  size[now_] = size[pre_] + 1;
  lson[now_] = lson[pre_], rson[now_] = rson[pre_];
  if(L_ >= R_) return ;
  int mid = (L_ + R_) >> 1;
  if(val_ > mid) Insert(rson[pre_], rson[now_], mid + 1, R_, val_);
  else Insert(lson[pre_], lson[now_], L_, mid, val_);
}
int Query(int u_, int v_, int L_, int R_, int ql_, int qr_) {
  if (ql_ <= L_ && R_ <= qr_) return size[v_] - size[u_];
  int mid = (L_ + R_) >> 1, ret = 0;
  if (ql_ <= mid) ret += Query(lson[u_], lson[v_], L_, mid, ql_, qr_);
  if (qr_ > mid) ret += Query(rson[u_], rson[v_], mid + 1, R_, ql_, qr_);
  return ret;
}
bool Judge(int len_, int a_, int b_, int c_) {
  int l = 1, r = rk[c_], up, down;
  while (l < r) {
    int mid = (l + r) >> 1;
    if (QueryLcp(mid + 1, rk[c_]) < len_) l = mid + 1;
    else r = mid;
  }
  up = r, l = rk[c_], r = n;
  while (l < r) {
    int mid = (l + r + 1) >> 1;
    if (QueryLcp(rk[c_] + 1, mid) < len_) r = mid - 1;
    else l = mid;
  }
  down = r;
  return Query(root[up - 1], root[down], 1, n, a_, b_ - len_ + 1) > 0;
}
int Solve(int a_, int b_, int c_, int d_) {
  int l = 0, r = std :: min(b_ - a_ + 1, d_ - c_ + 1);
  while (l < r) {
    int len = (l + r + 1) >> 1;
    if (Judge(len, a_, b_, c_)) l = len;
    else r = len - 1;
  }
  return r;
}
//=============================================================
int main() {
  n = read(), m = read();
  scanf("%s", S + 1);
  SuffixSort();
  for (int i = 1; i <= n; ++ i) Insert(root[i - 1], root[i], 1, n, sa[i]);
  for (int i = 1; i <= m; ++ i) {
    int a = read(), b = read(), c = read(), d = read();
    printf("%d\n", Solve(a, b, c, d));
  }
  return 0;
}

线段树合并

咕掉啦!

posted @ 2020-08-21 16:24  Luckyblock  阅读(108)  评论(0编辑  收藏  举报