Manacher算法

$$Manacher$$

【杂言】 :

任何的字符串算法在刚进行的时候,都会感觉很有难度, 唯一解决难度的问题, 就是多打多练,例如(Manacher),自然,也是有大佬直接一眼就可以看懂的,这也是我的一点理解


【准备】:

这也大多数的回文字符串可能用的,单独列一下。

  1. 首先在字符串的每一个字符之前或者之后都加入一个#,举个例子 :例如字符串(S = "abcd") , 根据这个意思,我们也就是要把他变成 (S=) "#a#b#c#d#" 至于为什么这么做?因为在进行回文字符串的判定时 ,字符串长度是偶数或奇数都会导致有不同的实现 , 十分的麻烦,同时,相比之下, 长度为奇数的 回文字符串更好判定, 所以转化为上述形式。关于一定会形成奇数的证明(相当于字符串 ( imes 2 +) #)。
  2. 为了防止每一次进行询问的时候都看一下是否会越界,我们在首尾添加上一个^和~,这种东西,当询问到那里的时候自动跳出就行了
  3. 但同时我们插入的这些所有的 字符,都必须保证和原字符保持互异 。
    综上 , 也就是把上面的字符串 ,改成了(S= ") ~#a#b#c#d#^(") 的这么一个基操,其实在普通的回文字符串的判定,基本上也会用到来优化代码;

【算法】:

通过举出一个例子来进行说明一下算法的流程;
例子 : (S="abbahopo") ,我们发现在本串(S)中存在两个回文子串, 一个是长度为偶数的子串("abba"),和长度为奇数的回文子串 ,("opo"),我们按照上方的准备,魔改一下,就出来了新的(S)(仍旧选用(S),应该是(S_{new})的),= " ~ #a#b#b#a#h#o#p#o# ^ ",经过魔改之后,其长度也就都是奇数了,然后再来考虑一下其寻找过程。

由于下面插入在使用(Markdown)时,用,#会导致语法异常,在这里省略掉开头和结尾的^,并且用(1)来代替#。

定义数组 (len_i) ,用于保存字符(i)的最大扩展长度,也就是回文半径, 同时 , 它也是去掉我们加的#字符的原数组的该扩展的字符串的总长度,例如下图中#o#p#o#,其最大回文半径就是(3),并且,该字符串的长度也就是(3)

[egin{array}{c|lcr} string.len& ext{1}& ext{2}& ext{3}& ext{4} & ext{5}& ext{6}& ext{7}& ext{8}& ext{9}& ext{10}& ext{11} & ext{12}& ext{13}& ext{14}& ext{15} & ext{16} & ext{17}\ hline S =& 1&a&1&b&1&b&1&a&1&h&1&o&1&p&1&o&1\ len =&1&2&1&2&5&2&1&2&1&1&1&2&1&4&1&2&1 \ end{array} ]

则,其回文字符串的长度,就是(len_{i}) - 1


重点就是对于(len_i)的求解,
设置两个变量(mx)(id)(mx)表示以(id)为中心的最长的回文子串的右边界, 那么则有,(mx = id + len_{id}) ,

如上图,假设我们现在进行求解(len_i),
也就是以(i)为中心最长回文半径,如果(i<mx)那就是上图, (len_i=min(len_{id * 2 - i} ,mx-i)) ,其中(len_{id * 2 - i})就是(i)关于(id)的对称点 , 即上图的(len_{j})

【复杂度】:

关于这个算法的复杂度, 显然我不会证明, 但是线性的。
结合下文的参考文献吧

【参考文献】:

1.https://segmentfault.com/a/1190000008484167
2.https://www.zhihu.com/question/37289584(这个倒是多)

【例题】:

给出一个只由小写英文字符 ( exttt a, exttt b, exttt c,ldots exttt y, exttt z)组成的字符串 (S) ,求 (S) 中最长回文串的长度 。

/*
 by : Zmonarch
 知识点 : Manacher
*/
#include <bits/stdc++.h>
using namespace std ;
const int kmaxn = 1e8 ;
int len[kmaxn] , ans;
int mx , id ,cnt ;
char s[kmaxn] , s1[kmaxn] ;//s1是旧的,s是新的
void prepare()
{
	//将原来的字符串搞出来 
	scanf("%s" , s1 +1) ;
	int n = strlen(s1+1) ;
	s[++cnt] = '~' ;
	s[++cnt] = '#' ; //第一个数之前别忘了插入 
	for(int i = 1 ; i <= n ; i++)
	{
		s[++cnt] = s1[i] ;
		s[++cnt] = '#' ;
	}
	s[++cnt] = '^' ;
}
void manacher()
{
	for(int i = 2 ; i <= cnt - 1; i++)//过滤掉首尾的标示符 
	{
		if(i <= mx) len[i] = min(len[id * 2  - i ] , mx - i + 1) ;
		else len[i] = 1 ; 
		while(s[i - len[i]] == s[i + len[i]]) 
		{
			len[i]++;
		}
		if(i + len[i] > mx) 
		{
			mx = i + len[i] - 1 ;
			id = i ;
		}
		//printf("%d
", ans) ; 
		ans = max(ans , len[i]) ;
	}
	printf("%d", ans-1) ;
}
int main()
{
	prepare() ;
	manacher() ;
	return 0 ;
} 
原文地址:https://www.cnblogs.com/Zmonarch/p/14227129.html