Manacher
用途:求以 i 为中心的回文子串的长度
技巧:加分隔符隔开每个字符
核心思路
- 维护一个 box,记录当前已知的最靠右的回文子串的左右端点 [l,r]
- 计算 d[i] 时,如果 i 在 box 内,就要找对称位置,l+(r-i)
- 如果对称点的回文半径不超出 box,放心转移 d[i]=d[l+r-i]
- 如果超出,就保守估计,d[i]=r-i+1
- 看视频的图理解
- 在 box 外就暴力枚举
- 求出 d[i] 后,看 box 的边界是否需要更新
来自 oi-wiki 的代码
c++
// 先预处理成 #a#b#c#
vector<int> d(n);
for (int i = 0, l = 0, r = -1; i < n; i++) {
int k = (i > r) ? 1 : min(d[l + r - i], r - i + 1);
while (0 <= i - k && i + k < n && s[i - k] == s[i + k]) {
k++;
}
d[i] = k--;
if (i + k > r) {
l = i - k;
r = i + k;
}
}
代码中 d[i] 表示 s(原串) 中以 i 为中心的极大回文子串的 总长度+1
如果要求 s 的最长回文子串长度怎么办?
c++
for (int i = 0; i < n; i++) {
mx = max(mx, d[i] - 1);
}
任意插入最少字符,使得串变回文:
答案是 n - 最长回文子串长度
扩展 kmp
Z 函数:对于字符串 s,z[i] 表示 s 与其后缀 s[i,n] 的最长公共前缀的长度
核心思路:
- 定义匹配段为区间 [i,i+z[i]-1],维护一个 box 表示最靠右的匹配段的左右端点 [l,r],那么 [l,r] 是 s 的前缀
- 计算 z[i] 时,同样分情况讨论,情况同马拉车中的讨论
- 在 box 内且不超出,z[i]=z[i-l+1]
- 超出时则令为 z[i]=r-i+1
- 其余情况暴力枚举
来自 oi-wiki 的代码
c++
vector<int> z_function(string s) {
int n = (int)s.length();
vector<int> z(n);
for (int i = 1, l = 0, r = 0; i < n; ++i) {
if (i <= r && z[i - l] < r - i + 1) {
z[i] = z[i - l];
} else {
z[i] = max(0, r - i + 1);
while (i + z[i] < n && s[z[i]] == s[i + z[i]]) ++z[i];
}
if (i + z[i] - 1 > r) l = i, r = i + z[i] - 1;
}
return z;
}
代码中规定 z[0] = 0,但实际上 z[0]=n,在使用模板时需注意
Z 函数的应用
给定串 和 ,如何求出 与 的最长公共前缀和最长公共后缀呢?
设 的长度为
对于前缀,可以通过获取 的 数组来获得,有
对于后缀,可以通过获取 的 数组并反转来获得,有
例题:对于 的长为 的窗口,能否做到连续修改 个字符,使得 ?
c++
class Solution {
vector<int> calc_z(string s) {
int n = s.length();
vector<int> z(n);
int box_l = 0, box_r = 0; // z-box 左右边界
for (int i = 1; i < n; i++) {
if (i <= box_r) {
z[i] = min(z[i - box_l], box_r - i + 1);
}
while (i + z[i] < n && s[z[i]] == s[i + z[i]]) {
box_l = i;
box_r = i + z[i];
z[i]++;
}
}
return z;
}
public:
int minStartingIndex(string s, string pattern) {
vector<int> pre_z = calc_z(pattern + s);
ranges::reverse(pattern);
ranges::reverse(s);
vector<int> suf_z = calc_z(pattern + s);
ranges::reverse(suf_z); // 也可以不反转,下面写 suf_z[suf_z.size() - i]
int m = pattern.length();
for (int i = m; i <= s.length(); i++) {
if (pre_z[i] + suf_z[i - 1] >= m - k) {
return i - m;
}
}
return -1;
}
};