Manacher 算法
Posted patt
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Manacher 算法相关的知识,希望对你有一定的参考价值。
利用回文串的「镜像」特点减少计算。
引理 0
设 $S$ 是一个长度为 $n+1$ 回文串,下标从 $0$ 开始;$T = S[l, r]$ 是 $S$ 的子串。$T$ 是回文串当且仅当 $S[n-r, n-l]$ 是回文串。
先考虑长度为奇数的回文子串(简称为「奇回文子串」),可以求出以每个下标为中心的最长奇回文子串的长度。
用 $P_i$ 表示以下标 $i$ 为中心的最长回文子串,用 $f(i)$ 表示 $P_i$ 的长度,即 $f(i) = |P_i|$ 。
用 $L_i, R_i$ 分别表示 $P_i$ 的左半部分和右半部分。用 $l_i,r_i$ 分别表示 $P_i$ 的左右端点的下标。
引理 1
设 $i$,$j$ 是两个下标。
(1) 若 $j > i$ 且 $r_j le r_i $ 则 $ P_j subseteq P_i $;
(2) 若 $j < i$ 且 $r_j le r_i $ 则 $R_j subseteq P_i$ 。
试着观察这种做法中的冗余计算。
字符串下标从 0 开始,$S[0, n)$ 。
假设当前以第 $i$ 为重心,考虑在 $i$ 之前是否有「镜子」$j$ 使得 $i$ 对着 $j$ 能照出自己。换言之是否存在 $j<i$ 使得以 $j$ 为中心的回文子串能够「波及」$i$ 。用式子表示就是 $f(j) ge 2(i-j) + 1 $ 或者 $j + f(j)/2 ge i$。$i$ 关于 $j$ 的镜像为 $2j -i$。
我们希望 $j$ 不仅能「照出」$i$ 还要能「照出」$i$ 右边尽量多的字符,换言之「照得尽量远」。用式子表示就是 $ j + f(j)/2$ 尽可能大。
回文串在镜像操作下保持不变。
若 $j + f(j)/2 ge i$ 那么区间 $[j-f(j)/2, j+f(j)/2]$ 中以 $i$ 为中心的最长回文子串即区间$[j - f(j)/2 , j + f(j)/2]$ 中以 $2j-i$ 为中心的最长回文子串。
这个性质我一直绕不过来。
对字符串进行镜像操作
示意图
字符串的镜像操作相当于序列反转(reverse),因此回文串在镜像操作下保持不变。
$S[j-f(j)/2, j+f(j)/2]$ 中的任意子串都有一个关于 $j$ 的镜像串(即反转串)。
Manacher 算法原理示意图
复杂度
观察上图。
可 $O(1)$ 地计算出 $|P_i igcap P_j|$ ;若 $P_i igcap P_j$ 能向两侧扩展,那么右边界(border, frontier)将增大;右边界是单调不减的,因此扩展的总复杂度为 $O(n)$ 。于是 Manacher 算法的复杂度为 $O(n)$ 。
实现
void manacher (char str [], int h[], int n) {
int m = 0;
static char buf[M]; // M是字符串最大长度的两倍。
//以'#'开头
for (int i = 0; i < n; ++i){
buf[m++] = '#', buf[m++] = str[i];
}
buf[m++] = '#'; // 以'#'结尾
buf[m] = '