https://segmentfault.com/a/1190000003914228
今天上午刷字符串题,有道题不会,原因是没有深入理解manacher算法,学习了这篇文章。
虽然没有完全理解,下次再来学习。
(1) 解决长度奇偶性带来的对称轴位置问题
Manacher算法首先对字符串做一个预处理,在所有的空隙位置(包括首尾)插入同样的符号,要求这个符号是不会在原串中出现的。这样会使得所有的串都是奇数长度的。以插入#号为例:
aba ———> #a#b#a#
abba ———> #a#b#b#a#
插入的是同样的符号,且符号不存在于原串,因此子串的回文性不受影响,原来是回文的串,插完之后还是回文的,原来不是回文的,依然不会是回文。
(2) 解决重复访问的问题
我们把一个回文串中最左或最右位置的字符与其对称轴的距离称为回文半径。Manacher定义了一个回文半径数组RL,用RL[i]表示以第i个字符为对称轴的回文串的回文半径。我们一般对字符串从左往右处理,因此这里定义RL[i]为第i个字符为对称轴的回文串的最右一个字符与字符i的距离。对于上面插入分隔符之后的两个串,可以得到RL数组:
char: # a # b # a #
RL : 1 2 1 4 1 2 1
RL-1: 0 1 0 3 0 1 0
i : 0 1 2 3 4 5 6
char: # a # b # b # a #
RL : 1 2 1 2 5 2 1 2 1
RL-1: 0 1 0 1 4 1 0 1 0
i : 0 1 2 3 4 5 6 7 8
上面我们还求了一下RL[i]-1。通过观察可以发现,RL[i]-1的值,正是在原本那个没有插入过分隔符的串中,以位置i为对称轴的最长回文串的长度。那么只要我们求出了RL数组,就能得到最长回文子串的长度。
于是问题变成了,怎样高效地求的RL数组。基本思路是利用回文串的对称性,扩展回文串。
代码实现:
const int maxn = 110000+100;
char s[maxn],str[maxn*2];
int p[maxn*2];
int manacher()
{
int i;
for(i=1;s[i];i++)
str[i*2]=s[i],str[i*2+1]='#';
str[0]='?',str[1]='#',str[i*2]='\0';
int res=0,k=0,maxk=0;
for(int i=2;str[i];i++)
{
p[i]=i<maxk?min(maxk-i,p[2*k-i]):1;
while(str[i-p[i]]==str[i+p[i]]) p[i]++;
if(p[i]+i>maxk)
k=i,maxk=i+p[i];
res=max(res,p[i]);
}
return res-1;
}