\(\text{Manacher 学习笔记}\)
一、引入
首先我们需要知道的是 \(\text{Manacher}\) 是解决回文串问题的有效工具。
一个通用的问题模型是给定一个长度为 \(n\) 的字符串 \(s\),统计该字符串中所有的回文子串的个数。\(\text{Manacher}\) 算法可以在 \(O(n)\) 的时间复杂度内解决这个问题。
我们知道的是,对于任意一个回文串 \(s[i,j]\),有对称中心 \(\lfloor \dfrac{i+j-1}{2}\rfloor\)。考虑到奇数和偶数长度回文串的差异,我们用两个数组 \(d_1(i),d_2(i)\) 分别表示长度为奇数、偶数的回文串中心为 \(i\) 时回文串的个数。
现在我们的问题就是快速求出 \(d_1,d_2\) 两个数组。
二、算法
我们先考虑 \(d_1\) 的求解过程,求出 \(d_1\) 数组的基本思想是采用之前维护过的 \(d_1\) 数组信息来更新现有的 \(d_1\) 数组。为了叙述方便,下文以 \(d\) 数组代指 \(d_1\) 数组。
既然这样,在求 \(d(i)\) 时我们需要维护 \([1,i-1]\) 范围内边界最靠右的回文串的边界 \(l,r\),初始设 \(l=1,r=0\)。
现在考虑计算 \(d(i)\) 的方式:
- 若 \(i>r\),暴力匹配;
- 若 \(i\le r\):
由于 \([l,r]\) 是回文串,那么 \(d(i)=d(l+r-i)\),也就是将中心为 \(l+r-i\) 为中心的回文串 copy 到了 \(i\) 上。然而需要留意的是,我们只能保证在 \([l,r]\) 内的部分是回文串,而这个部份内回文串的最大长度显然是 \(r-i+1\)。
考虑复杂度分析:\(r\) 是单调递增的,而暴力算法运行一次一定会使得 \(r\) 增长 \(1\),因此复杂度为 \(O(n)\)。
现在考虑 \(d_2(i)\) 的求解——但其实我们有更好的方法用一套模板解决两个问题。
考虑在原字符串每个字符的两侧都插入一个其它字符,这样一来所有回文串都可以归约到奇数串的情况,例如 aba
\(\to\) a#b#a
,abba
\(\to\) a#b#b#a
。对于多统计的情形,容易得到关系 \(d(i)=2\times d_1(i)=2\times d_2(i)+1\)。
给出代码:
void manacher() {
int l = 1, r = 0;
for (int i = 1; i <= n; i++) {
int k = (i > r) ? 0 : min(d[l + r - i], r - i + 1);
while (i + k <= n && i - k >= 1 && equ(s[i - k], s[i + k])) ++k;
d[i] = k--;
if (i + k > r) l = i - k, r = i + k;
}
}
三、应用
应用典型是求最长回文子串。对于长度为奇数的回文串,最长的长度是 \(2\times d_1-1=d-1\),偶数是 \(2\times d_2=d-1\)。于是返回 \(\max_d-1\) 即可。
标签:奇数,Manacher,笔记,times,学习,数组,长度,回文 From: https://www.cnblogs.com/Rock-N-Roll/p/18652234