Manacher算法
$$Manacher$$
【杂言】 :
任何的字符串算法在刚进行的时候,都会感觉很有难度, 唯一解决难度的问题, 就是多打多练,例如\(Manacher\),自然,也是有大佬直接一眼就可以看懂的,这也是我的一点理解
【准备】:
这也大多数的回文字符串可能用的,单独列一下。
- 首先在字符串的每一个字符之前或者之后都加入一个#,举个例子 :例如字符串\(S = "abcd"\) , 根据这个意思,我们也就是要把他变成 \(S=\) "#a#b#c#d#" 至于为什么这么做?因为在进行回文字符串的判定时 ,字符串长度是偶数或奇数都会导致有不同的实现 , 十分的麻烦,同时,相比之下, 长度为奇数的 回文字符串更好判定, 所以转化为上述形式。关于一定会形成奇数的证明(相当于字符串 \(\times 2 +\) #)。
- 为了防止每一次进行询问的时候都看一下是否会越界,我们在首尾添加上一个^和~,这种东西,当询问到那里的时候自动跳出就行了
- 但同时我们插入的这些所有的 字符,都必须保证和原字符保持互异 。
综上 , 也就是把上面的字符串 ,改成了\(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\)。
则,其回文字符串的长度,就是\(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 ;
}