manacher算法
manacher,又叫马拉车。
马拉车可以用来求最长回文子串。
思考如何求最长回文子串,我们首先会想到一个 \(O(n^3)\) 的解法:枚举所有子串,然后判断是否为回文串。当然这个解法非常大 蠢。
然后,我们又会有一个想法,枚举每个点,然后从这个点向外拓展,如果相同则拓展,否则记录答案。这里有个问题,就是回文串的长度可能是偶数,也就是没有中间点,那么我们可以在每两个字符之间添加 '#',这样就有了中间点了,像这样:
注意,前面的 '@' 是防止数组越界的,如果扫到头尾都是回文串,那么如果继续扫下去就会越界,而这里有一个 '@' 就可以防止越界的发生。
那么此时这个回文串真正的长度应该为 半径(包含中间点)-1。
这个算法的时间复杂度是 \(O(n^2)\)的。
我们思考上面那个算法,发现他有许多位置是重复枚举的,比如:
橙色线中间部分是一个回文串,红色线中间部分也是一个回文串,圈起来的是回文串的中心。
可以发现,这两个回文串会有重复的地方,那么有没有方法避免这些重复枚举呢?答案是有的。
首先,我们记录一个最大的 \(r\) 和对应的 \(mid\),使得 \(mid - (r - mid)\)~\(r\) 是一个回文串。(这里的 \(mid\)其实就是回文串中心的位置啦)
然后,若我们当前查询的节点 \(now\) 在 \(r\) 之内:
那么,既然在一个回文串的右半部分(因为是从前往后扫,所以一定在 \(mid\) 右边,是有半部分),则一定有一个对应的左半部分的字符(下面简称 \(lc\) QwQ):
而 \(lc\) 的半径是已经求出的,若 \(lc\) 的半径是在这个大回文串之内,那么 \(now\) 半径就和 \(lc\) 的一样。但如果以 \(lc\) 为中心的字符串不全在大回文串之中呢?比如:
那么,此时我们能确定的半径就是 \(r-now\),也就是这一段:
那么,我们只要在上面两个中取一个最小值即可。
code:
#include
#include
using namespace std;
string s;
int mid,r,ans,b[22000005];//b[i]记录以i为中心的回文串的半径
int min(int x,int y){return x'z') ch=getchar();
while(ch>='a'&&ch<='z') ss+=ch,ss+='#',ch=getchar();
return ;
}
int main()
{
read(s);
for(int i=1;i<(int)s.length();i++)
{
if(i<=r) b[i]=min(b[mid*2-i],r-i+1);
while(s[i-b[i]]==s[i+b[i]]) b[i]++;
if(i+b[i]-1>r) r=i+b[i]-1,mid=i;//更新r和mid
if(b[i]-1>ans) ans=b[i]-1;
}
printf("%d\n",ans);
return 0;
}