【字符串】 Z-algorithm

Z-algorithm

Algorithm

Task

给定一个文本串 S 和一个模式串 T,求 T 对于 S 的每个后缀子串的公共前缀子串。

Limitations

要求时空复杂度均为线性

Solution

X 是一个字符串,则以下表述中,Xu 代表 X 的第 u 个字符,Xuv 代表 X 的从 u 起到 v 结束的字串。

n=|S|, m=|T|

考虑按照长度由大到小扫描 S 的后缀字串,设当前要求 SinT 的公共前缀子串,则 k[1, i), Skn 的答案都已计算完成。

设先前的计算中,匹配到 S 最远的一次为第 p 次,即 p+ansp 在所有 k+ansk 中最大,设 q=p+ansp。显然有 p<i

首先不妨设 qiq<i 的情况将在下方说明。

j=ip+1,不难发现 Tj=Si,即 jSi 的对应匹配位置。

由于所求是公共前缀字串,因此有

Spq=T1ansp

引入一组辅助变量,设 nextjTjmT 的最长公共前缀子串长度。

根据定义,有

Tjj+nextj=T1nextj

分两种情况讨论。

第一种情况,j+nextj<q,即 Tjj+nextjSpq 的字串,因此有

Tjj+nextj=Sii+nextj

又因为 Tjj+nextj=T1nextj(已证),等量代换得到

Sii+nextj=T1nextj

对于 Si+nextj+1,则可以用反证法证明其不等于 Tnextj+1,否则由于 Tj+nextj+1 依然是 Spq 的字串,所以 Tj+nextj+1=Tnextj+1,这与 nextj 是最长前缀公共子串矛盾。

因此,对于这种情况,答案即为 nextj

第二种情况,j+nextjq,即 Tjj+nextj 不全是是 Spq 的字串,因此有

首先可以用与第一种情况相同的证明方式证明 Siq=T1qi+1,即 q 及以前的字符可以与 T 完美匹配,而对于 q 后面的字符,我们暴力将其与 T 匹配,同时更新 pq 的位置即可。

对于 q<i 的情况,显然 q=i1,直接继续暴力进行匹配即可。

考虑时间复杂度:

除掉暴力匹配的环节,剩下的部分显然都是单次 O(1) 完成,因此这一部分的复杂度是线性的。

考虑每次暴力匹配都会让 q 右移,所以暴力匹配的次数是线性的,而单次暴力匹配是 O(1) 的,因此算法的时间复杂度是线性的。

考虑 next 数组的计算:我们发现这相当于令文本串 S=T,只需要预处理 next1next2,可以发现从 next3 起,计算所需要的 next 值都已经在之前被计算过。

Sample

【P5410】 【模板】扩展 KMP

Description

给定一个文本串 S 和一个模式串 T,求 T 对于 S 的每个后缀子串的公共前缀子串。并输出 T 的每个后缀字串与 T 的公共前缀子串长度。

Limitations

字符串长度不超过 105

Solution

板板题。算法在实现上比较吃细节,注意比较大于小于的时候是否应该加等于号。记得对拍

Code

#include <cstdio>
#include <cstring>
#include <algorithm>

const int maxn = 100005;

int nxt[maxn];
char S[maxn], T[maxn];

void Z_algorithm(const char *const A, const char *const B, const int x, const int y, const bool pt);

int main() {
  freopen("1.in", "r", stdin);
  scanf("%s\n%s", S + 1, T + 1);
  int x = strlen(S + 1), y = strlen(T + 1);
  nxt[1] = y;
  Z_algorithm(T, T, y, y, false);
  for (int i = 1; i <= y; ++i) {
    qw(nxt[i], i == y ? '\n' : ' ', true);
  }
  Z_algorithm(S, T, x, y, true);
  putchar('\n');
  return 0;
}


void Z_algorithm(const char *const A, const char *const B, const int x, const int y, const bool pt) {
  int p = 0, q = 1;
  if (!pt) {
    while ((q < x) && (A[q] == A[q + 1])) ++q;
    nxt[p = 2] = q - 1;
    q = std::max(q, 2);
  } else {
    while ((q <= x) && (q <= y) && (A[q] == B[q])) ++q;
    p = 1;
    qw(--q, ' ', true);
  }
  for (int i = pt ? 2 : 3, _ans; i <= x; ++i) {
    int a = i - p + 1;
    int len = nxt[a];
    if ((i + len - 1) >= q) {
      _ans = std::max(0, q - i + 1);
      while ((q < x) && (_ans < y) && (A[q+1] == B[_ans+1])) {
        ++_ans; ++q;
      }
      q = std::max(p = i, q);
    } else {
      _ans = len;
    }
    if (pt) {
      qw(_ans, ' ', true);
    } else {
      nxt[i] = _ans;
    }
  }
}
posted @   一扶苏一  阅读(452)  评论(1编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示