欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

manacher 算法

程序员文章站 2024-03-16 12:46:28
...

序言

manacher 好像有点不熟练了,怕翻车了,写篇文章理理思路。


目标

给定一个字符串 SS,以 O(n)O(n) 的复杂度,求出以每一个点为中心最长的回文子串的长度。

预处理

首先对于字符串如 abaccaa,将其两两字符之间插入一个#,并在首尾各插入一个不常用的字符(避免边界问题),如变为?#a#b#a#c#c#a#a#$,这样的一个新串在处理时就无需考虑长度为偶数的回文子串,如回文子串 acca 在新串中对应的 #a#c#c#a#,且新串中回文子串的半长恰为原来的两倍。

暴力

对于每一个字符,暴力枚举比对以其为中心的最长回文子串的长度,O(n2)O(n^2)


manacher

manacher 利用回文串的性质,通过对不同情况的讨论处理,完成对回文子串的 O(n)O(n) 求解。

回文串性质

给一个回文串 SS,如 cabaemeabac,我们发现这个的左半边包含了一个回文子串aba,那么其右半边对称的位置也一定有这样一个回文子串aba,反之亦然。

形式化:S={a1a2...an1an}S=\{a_1a_2...a_{n-1}a_n\} 是回文串,ijinj+1{aiai+1...aj1aj}\exist i \le j,i \neq n-j + 1,\{a_ia_{i+1}...a_{j-1}a_j\} 是回文子串,那么 {anj+1anj+2...aniani+1}\{a_{n-j+1}a_{n-j+2}...a_{n-i}a_{n-i+1}\} 也一定是回文子串。

同样的如果不是回文子串,那么对称的位置也一定不为回文子串。

三个标记和一个数组:

P:Position,当前字符所在位置;
R:Right,当前最靠右的最长回文子串的右端点所在位置;
C:Center,当前最靠右的最长回文子串的中心所在位置;
r[i]:表示以第 ii 个字符为中心的最长回文子串长度半长,所以 i+r[i]i+r[i] 即为以第 ii 个字符为中心的最长回文子串的右端点所在位置。

分类讨论:

  1. P>RP > Rr[P]=1r[P]=1
  2. PRP \le R,找到 PP 关于 CC 的对称位置 DDD=C(PC)=2CPD =C-(P-C)=2C-P
    2.1. P+r[D]RP+r[D] \le R,根据性质,r[P]=r[D]r[P] = r[D]
    2.2. P+r[D]>RP+r[D] > R,根据性质,r[P]=RPr[P] = R-P(认真看一下性质)。

对于 112.22.22.12.1 如果暴力找显然直接结束)都还可以以 PP 为中心寻找更长的回文子串(暴力查找),然后更新 CCRR

复杂度分析

复杂度的瓶颈显然在于暴力查找匹配最长回文子串的过程。

每次暴力查找都是从 RR 的位置开始找,且每次找完都一定会向右更新 RR,所以暴力查找次数等于 RR 的最大值,因为 RSR \le |S|,暴力查找最多进行 S|S| 次,所以复杂度为 O(n)O(n)


代码

void Manacher()
{
	int C = 1, R = 1;
	r[1] = 1;
	for(int P = 1; P <= n; ++P)
	{
		r[i] = min(R - P + 1, r[C * 2 - P]); //讨论了1,2.1和2.2
		while(S[P - r[P]] == S[P + r[P]]) ++r[P]; //暴力判断回文子串
		if(R < P + r[i] - 1){ R = P + r[P] - 1; C = P; } //更新C和P
	}
}