【字符串】【P5830】 【模板】失配树

【字符串】【P5830】 【模板】失配树

Description

给定一个长度为 \(n\) 的字符串 \(S\),有 \(m\) 次询问,每次询问给定 \(S\) 的两个前缀,求它们的最长公共 border 的长度。

最长公共 border 的含义为,对于一个字符串 \(T\),设其 Border 集合为所有既是 \(S\) 的前缀子串又是 \(S\) 的后缀子串的集合,两个字符串的最长公共 border 为两个字符串的 Border 集合的交集中长度最长的字符串。

Limitations

\(1 \leq n \leq 10^6\)

\(1 \leq m \leq 10^5\)

Solution

注意,这篇题解不是这个模板的标准做法,也不是最简单的做法。

两个前缀的最长公共 border 即为他们在 border 树上的 LCA

因为刚起床就被 fa姐姐 拉来验题,脑袋昏昏忘记了这个结论,只能再口胡一个铁憨憨做法。

注意到所求的 border 一定既是第一个字符串的后缀,又是第二个字符串的后缀,因此一定是两个字符串的公共后缀 ,同时注意到由于这两个字符串的前缀是相同的,所以如果一个字符串 \(T\) 既是其中任意一个串的 border,又是两个串的公共后缀,那么它一定是两个串的公共 border。并且这个条件显然也是必要条件,因此我们在求出两串的 lcp 以后只需要在其中任意一个串上找到其最长的长度不超过 lcp 长度的 border,那么该串即为两串的最长公共 border

假设我们已经求出了两串的 lcp 长度,那么问题就只剩下对一个字符串求其最长的长度不超过某数的 border

我们考虑对每个前缀,将它向它的最长 border 连一条边,那么显然这个图有 \((n + 1)\) 个节点, \(n\) 条边,又因为这个图是联通的,根据树的判定定理,这个图是一棵树,若规定 \(0\) 是这棵树的根,数学归纳可得每个节点的父节点为该节点所代表的前缀的最长 border。因为一个节点的 border 显然比该节点的长度小,所以任何一个节点到根所在的链上,若将节点按深度从小到大排列,则其所代表的前缀长度一定是单调递增的。因此我们只需要对整棵树进行 dfs,同时用一个栈维护当前节点到根的链,然后在栈里二分即可找到所求的串。

border 的方法见 【P3375】KMP字符串匹配

而求两个前缀的 lcp,可以对原串建立一个 SAM,两个前缀在 parent 树上所对应节点的 LCA 即为他们的 lcp。也可以将原串反过来,转化为求两个后缀的最长公共前缀,求出 SA 后用 height 数组解决。

但是扶苏既不愿意将原串反过来求 SA 在写个 ST,也担心毒瘤出题人卡了空间以后 SAM 建出来会爆空间,因此扶苏选择了 二分+hash 求出其 lcp

显然公共后缀的长度满足二分性,因此只要选择一个满足前缀可减性的 hash 函数就可以 \(O(1)\) check 了。

考虑时间复杂度:二分求 lcp 的复杂度是 \(O(m \log n)\),在 border 树上二分的复杂度是 \(O(m \log n)\),因此总时间复杂度 \(O(n + m \log n)\)

Code

本来扶苏写了个四模数 hash,然后被卡常了就尝试减少模数个数,最后发现单模数就可以了(雾

#include <cstdio>
#include <vector>
#include <algorithm>

const int maxh = 4;
const int maxm = 100005;
const int maxn = 1000005;

const int MOD[] = {998244353, 1000000007, 1000000009, 1145141};

int n, m, top = -1;
char S[maxn];
int border[maxn], ans[maxm], stk[maxn];
std::vector<int> son[maxn], query[maxn];

struct HASH {
  int md;
  ll hash[maxn], inv[maxn];

  ll mpow(const int a, int d, const int p) {
    ll ret = 1, tmp = a;
    while (d) {
      if (d & 1) {
        (ret *= tmp) %= p;
      }
      (tmp *= tmp) %= p;
      d >>= 1;
    }
    return ret;
  }

  void build(const int x) {
    md = x;
    ll tmp = 1, iv = mpow(100, x - 2, x);
    inv[0] = 1;
    for (int i = 1; i <= n; ++i) {
      hash[i] = (hash[i - 1] + (S[i] - 'a') * tmp) % md;
      inv[i] = inv[i - 1] * iv % md;
      (tmp *= 100) %= md;
    }
  }

  bool check(const int x, const int y, const int len) {
    ll h1 = (hash[x] - hash[x - len]) * inv[x - len] % md, h2 = (hash[y] - hash[y - len]) * inv[y - len] % md;
    if (h1 < 0) h1 += md;
    if (h2 < 0) h2 += md;
    if (h1 != h2) {
      return false;
    } else {
      return true;
    }
  }
};
HASH h[maxh];

int ReadStr(char *p);
void dfs(const int u);

int main() {
  freopen("1.in", "r", stdin);
  n = ReadStr(S);
  for (int i = 0; i < maxh; ++i) {
    h[i].build(MOD[i]);
  }
  for (int i = 2, j = 0; i <= n; ++i) {
    while (j && (S[j + 1] != S[i])) {
      j = border[j];
    }
    if (S[j + 1] == S[i]) {
      ++j;
    }
    son[border[i] = j].push_back(i);
  }
  son[0].push_back(1);
  qr(m);
  for (int p, q, Ans, i = 1; i <= m; ++i) {
    p = q = Ans = 0; qr(p); qr(q);
    for (int l = 1, r = std::min(p, q) - 1, mid = (l + r) >> 1; l <= r; mid = (l + r) >> 1) {
      bool flag = true;
      for (int i = 0; i < maxh; ++i) if ((flag = h[i].check(p, q, mid)) == false) {
        break;
      }
      if (flag) {
        l = (Ans = mid) + 1;
      } else {
        r = mid - 1;
      }
    }
    ans[i] = Ans;
    query[std::min(p, q)].push_back(i);
  }
  dfs(0);
  for (int i = 1; i <= m; ++i) {
    qw(ans[i], '\n', true);
  }
  return 0;
}

int ReadStr(char *p) {
  auto beg = p;
  do *(++p) = IPT::GetChar(); while ((*p >= 'a') && (*p <= 'z'));
  *p = 0;
  return p - beg - 1;
}

void dfs(const int u) {
  stk[++top] = u;
  for (auto v : query[u]) {
    int w = ans[v]; ans[v] = 0;
    for (int l = 1, r = top, mid = (l + r) >> 1; l <= r; mid = (l + r) >> 1) if (stk[mid] <= w) {
      ans[v] = stk[mid];
      l = mid + 1;
    } else {
      r = mid - 1;
    }
  }
  for (auto v : son[u]) {
    dfs(v);
  }
  --top;
}

posted @ 2019-12-18 23:49  一扶苏一  阅读(371)  评论(0编辑  收藏  举报