Manacher算法

$$Manacher$$

【杂言】 :

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


【准备】:

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

  1. 首先在字符串的每一个字符之前或者之后都加入一个#,举个例子 :例如字符串\(S = "abcd"\) , 根据这个意思,我们也就是要把他变成 \(S=\) "#a#b#c#d#" 至于为什么这么做?因为在进行回文字符串的判定时 ,字符串长度是偶数或奇数都会导致有不同的实现 , 十分的麻烦,同时,相比之下, 长度为奇数的 回文字符串更好判定, 所以转化为上述形式。关于一定会形成奇数的证明(相当于字符串 \(\times 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\)

\[\begin{array}{c|lcr} string.len& \text{1}&\text{2}&\text{3}& \text{4} & \text{5}& \text{6}& \text{7}& \text{8}& \text{9}& \text{10}& \text{11} & \text{12}&\text{13}&\text{14}& \text{15} & \text{16} &\text{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(这个倒是多)

【例题】:

给出一个只由小写英文字符 \(\texttt a,\texttt b,\texttt c,\ldots\texttt y,\texttt 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\n", ans) ; 
		ans = max(ans , len[i]) ;
	}
	printf("%d", ans-1) ;
}
int main()
{
	prepare() ;
	manacher() ;
	return 0 ;
} 
posted @ 2021-01-03 22:03  SkyFairy  阅读(83)  评论(0编辑  收藏  举报