如何用 KMP 通过洛谷 P5410

我们需要求字符串 \(S\) 的 Z 函数。假设其长度为 \(n\)

不妨设 \(\operatorname{lcp}(S,S[i,n])=len\),那么 \(len\)\(S[1,i+len-1]\) 的一个 border。

可以对于一个前缀 \(S[1,i]\),动态维护其所有 border。如果一个 \(len\)\(i\) 的 border 但不是 \(i+1\) 的 border,那么就将 \(z_{i-len+1}\) 更新。

当前缀从 \(i\) 移动到 \(i+1\) 时,会删掉若干个 border,其余 border 长度 \(+1\),并有可能加入长为 \(1\) 的 border。因此我们只需要支持快速删除所有不合法的 border。

考虑维护一个 \(jp_x\),表示失配树上 \(x\) 的最深的祖先 \(y\),满足 \(s_{x+1}\neq s_{y+1}\)

现在我们考虑 \(x\) 是否合法,如果 \(s_{x+1}\neq s_{i+1}\) 则删除 \(x\),然后 \(x\leftarrow nxt_x\);否则 \(x\leftarrow jp_x\)。这样跳两次就能删一个 border,因此复杂度线性。

现在要考虑对每个 \(T\) 的后缀,求出其与 \(S\)\(\operatorname{lcp}\)

观察 kmp 的过程,实际上我们对于 \(T\) 的每个前缀 \(i\),都找出了最大的 \(x\),满足 \(S[1,x]=T[i-x+1,i]\)。其所有 border 即为所有满足条件的 \(x\)

每次依然去暴力删除所有不合法的 \(x\) 来更新 \(p\),复杂度同样是线性。

int main() {
  file();
  ios::sync_with_stdio(0), cin.tie(0);
  cin >> t >> s;
  m = t.size(), t = " " + t + " ";
  n = s.size(), s = " " + s + "@";
  z[1] = n;
  for(int i = 2, x = 0; i <= n; i++) {
    for(; x && (s[x + 1] != s[i]); ) {
      x = nxt[x];
    }
    if(s[x + 1] == s[i]) x++;
    nxt[i] = x;
    if(s[x + 1] == s[i + 1]) {
      jp[i] = jp[x];
    }else {
      jp[i] = x;
    }
    for(int j = x; j; ) {
      if(s[j + 1] == s[i + 1]) {
        j = jp[j];
      }else {
        z[i - j + 1] = max(z[i - j + 1], j);
        j = nxt[j];
      }
    }
  }
  for(int i = 1, x = 0; i <= m; i++) {
    for(; x && (s[x + 1] != t[i]); ) {
      x = nxt[x];
    }
    if(s[x + 1] == t[i]) x++;
    for(int j = x; j; ) {
      if(s[j + 1] == t[i + 1]) {
        j = jp[j];
      }else {
        p[i - j + 1] = max(p[i - j + 1], j);
        j = nxt[j];
      }
    }
  }
  ull ansz = 0, ansp = 0;
  for(int i = 1; i <= n; i++) {
    ansz ^= (ull)i * (z[i] + 1);
  }
  cout << ansz << "\n";
  for(int i = 1; i <= m; i++) {
    ansp ^= (ull)i * (p[i] + 1);
  }
  cout << ansp << "\n";
  return 0;
}
posted @ 2024-11-28 12:20  CJzdc  阅读(47)  评论(0)    收藏  举报