← 全部文章

Z 函数 学习笔记

本文目录(6 节)
  1. 定义 zz 函数
  2. O(n)\mathcal O(n) 求 zz 函数。
  3. 【模板】P5410 【模板】扩展 KMP(Z 函数)
  4. 【练习】CF126B Password
  5. 【练习】CF432D Prefixes and Suffixes
  6. 参考资料

Z 函数,又称扩展 KMP (exkmp),可以 O(n)\mathcal O(n) 求出一个字符串的所有后缀与这个字符串的 LCP 长度。

定义 zz 函数

对于字符串 ss,ziz_i 定义为 ∣LCP⁡(s,s[i:])∣|\operatorname{LCP}(s, s[i:])|,即从 ii 开始的后缀与 ss 的最长公共子串的长度。

暴力求显然是 O(n2)\mathcal O(n^2) 的,通常这个时间复杂度都无法被接受。

O(n)\mathcal O(n) 求 zz 函数。

维护 rr 最大的匹配子串 s[l:r]s[l:r]。

当 i≤ri \le r 时,我们充分利用已经算出来的 zz,直接把 ziz_i 初始化为 min⁡(zi−l+1,r−i+1)\min(z_{i-l+1}, r - i + 1);否则初始化为 00。

然后暴力往后匹配得到 ziz_i 的最终值,若 i+zi−1>ri+z_i-1 > r,还要更新 l,rl,r。

这样做时间复杂度是 O(n)\mathcal O(n) 的,因为每个字符最多被暴力匹配一次。

注意 z1z_1 必须单独处理。

inline void Z(char *s, int n) {
	for (int i = 1; i <= n; i++) z[i] = 0;
	z[1] = n;
	for (int i = 2, l = 0, r = 0; i <= n; i++) {
		if (i <= r) z[i] = min(z[i-l+1], r - i + 1);
		while (i + z[i] <= n && s[i+z[i]] == s[z[i]+1]) ++z[i];
		if (i + z[i] - 1 > r) l = i, r = i + z[i] - 1;
	}
}

【模板】P5410 【模板】扩展 KMP(Z 函数)

const int N = 2e7 + 7;
int n, m, z[N], p[N];
char a[N], b[N];

inline void Z(char *s, int n) {
	for (int i = 1; i <= n; i++) z[i] = 0;
	z[1] = n;
	for (int i = 2, l = 0, r = 0; i <= n; i++) {
		if (i <= r) z[i] = min(z[i-l+1], r - i + 1);
		while (i + z[i] <= n && s[i+z[i]] == s[z[i]+1]) ++z[i];
		if (i + z[i] - 1 > r) l = i, r = i + z[i] - 1;
	}
}

inline void exkmp(char *s, int n, char *t, int m) {
	Z(t, m);
	for (int i = 1; i <= n; i++) p[i] = 0;
	for (int i = 1, l = 0, r = 0; i <= n; i++) {
		if (i <= r) p[i] = min(z[i-l+1], r - i + 1);
		while (i + p[i] <= n && s[i+p[i]] == t[p[i]+1]) ++p[i];
		if (i + p[i] - 1 > r) l = i, r = i + p[i] - 1;
	}
}

int main() {
	rds(a, n), rds(b, m);
	exkmp(a, n, b, m);
	ll ans = 0;
	for (int i = 1; i <= m; i++) ans ^= 1ll * i * (z[i] + 1);
	print(ans);
	ans = 0;
	for (int i = 1; i <= n; i++) ans ^= 1ll * i * (p[i] + 1);
	print(ans);
	return 0;
}

【练习】CF126B Password

const int N = 1e6 + 7;
int n, z[N], v[N];
char a[N];

inline void Z(char *s, int n) {
	for (int i = 1; i <= n; i++) z[i] = 0;
	z[1] = n;
	for (int i = 2, l = 0, r = 0; i <= n; i++) {
		if (i <= r) z[i] = min(z[i-l+1], r - i + 1);
		while (i + z[i] <= n && s[i+z[i]] == s[z[i]+1]) ++z[i];
		if (i + z[i] - 1 > r) l = i, r = i + z[i] - 1;
	}
}

int main() {
	rds(a, n);
	Z(a, n);
	for (int i = 2; i <= n; i++)
		if (!v[z[i]]) {
			while (!v[z[i]]) v[z[i]--] = 1;
		} else if (i + z[i] - 1 == n) {
			while (i <= n) putchar(a[i++]);
			return 0;
		}
	prints("Just a legend");
	return 0;
}

【练习】CF432D Prefixes and Suffixes

const int N = 1e5 + 7;
int n, z[N], c[N], ans[N];
char a[N];

inline void add(int x, int k) {
	while (x <= n) c[x] += k, x += x & -x;
}

inline int ask(int x) {
	int k = 0;
	while (x) k += c[x], x -= x & -x;
	return k;
}

inline void Z(char *s, int n) {
	for (int i = 1; i <= n; i++) z[i] = 0;
	z[1] = n;
	for (int i = 2, l = 0, r = 0; i <= n; i++) {
		if (i <= r) z[i] = min(z[i-l+1], r - i + 1);
		while (i + z[i] <= n && s[i+z[i]] == s[z[i]+1]) ++z[i];
		if (i + z[i] - 1 > r) l = i, r = i + z[i] - 1;
	}
}

int main() {
	rds(a, n);
	Z(a, n);
	for (int i = 1; i <= n; i++) {
		add(1, 1), add(z[i] + 1, -1);
		if (i + z[i] - 1 == n) ans[z[i]] = ask(z[i]), ++ans[0];
	}
	print(ans[0]);
	for (int i = 1; i <= n; i++)
		if (ans[i]) print(i, ' '), print(ans[i]);
	return 0;
}

参考资料

评论

    填写昵称即可留言。评论经站主审核后公开,请勿提交私人信息。

    正在连接评论服务…