Manacher 学习
先来一段代码压压惊:
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 1e5+5;
char str[maxn],str2[maxn*2];
int Len[maxn];
int string_init() {
str2[0] = '$';
str2[1] = '#';
int len = 2;
int length = strlen(str+1);
for (int i = 1;i<=length;i++) {
str2[len++] = str[i];
str2[len++] = '#';
}
str2[len-1] = '$';
str2[len] = '\0';
return len;
}
int Manacher() {
int len = string_init();
int mxlen = -1,maxr = -1,middle = -1;
for (int i = 1;i < len;i++) {
if ( maxr < i ) {
int j = 1,cnt = 1;
while ( str2[i-j] == str2[i+j] ) j++,cnt += 2;
Len[i] = cnt; middle = i; maxr = i + j;
mxlen = max(mxlen,Len[i]);
} else if ( maxr > i && Len[2*middle-i] == (maxr-i) ) {
int j = maxr - i,cnt = Len[2*middle-i];
while ( str2[i-j] == str2[i+j] ) j++,cnt+=2;
Len[i] = cnt; middle = i; maxr = i + j;
mxlen = max(mxlen,Len[i]);
} else if ( maxr >= i ) {
Len[i] = min(Len[2*middle-i],maxr-i);
}
}
return mxlen;
}
int main() {
while(~scanf("%s",str+1)){
printf("%d\n",(Manacher()+1)/2);
}
return 0;
}
想像一下对于一个字符串,如何求它的最长的回文子串,暴力的做法就是枚举每一个点然后往两边扩,但是这个算法的时间复杂度是O(n2)O(n^2)O(n2)时间很不进人意,于是就诞生了Manacher算法
Manacher
开始Manacher之前我们首先要准备两个变量maxr和middle ,maxr 和 middle 依次表示 回文串的最大右边界,和 最大右边界的回文串的中心
(1).
如果maxr小于当前要查找的回文中心,我们就暴力寻找(这一点可以参见,我的Manacher函数中的第一个if)
if ( maxr < i ) {
int j = 1,cnt = 1;
while ( str2[i-j] == str2[i+j] ) j++,cnt += 2;
Len[i] = cnt; middle = i; maxr = i + j;
mxlen = max(mxlen,Len[i]);
}
(2).
请看图片如果当前要寻找的位置 iii,在maxr里面我们可以在回文的对称一边找到 i′i'i′ 并且如果i′i'i′的回文串左边界比maxl大的话,那么我当前位置iii的回文子串的长度就跟i′i'i′的一样
else if ( maxr >= i ) { // 为什么是min(Len[2*middle-i],maxr-i);呢往下看
Len[i] = min(Len[2*middle-i],maxr-i);
}
(3).
在第二种的基础上,我的i′i'i′的回文串的左边界比maxl要小,但是此时iii的回文串的长度为maxr−imaxr - imaxr−i 可以证明一下为什么不等于i′i'i′的回文串长度,如果我当前位置的回文子串长度等于i′i'i′的话,那么在maxl和maxr两端的外部是不是还有一段连续相等的串,既然这样的话我的middle得回文串的长度应该更大,可是事实没有更大,因此当前位置的回文子串的长度为maxr−imaxr - imaxr−i
######(4).
最后一种情况 : 当我的 iii位置的对称点回文左边界刚好与maxl重合
因为不知道当前位置外面的串的情况,因此对于这种情况暴力寻找回文串
###时间复杂度估计
因为我的maxr不断向右走并且没有重复走的情况等于说我只遍历了一遍串因此时间复杂度为O(n)O(n)O(n)