manacher 算法是用于解决字符串中最长回文子串问题的著名算法。
给定包含小写拉丁字母的字符串 \(S\),求其最长回文子串的长度。
首先想到暴力解法,枚举左右端点(\(O(n^2)\)),再 \(O(n)\) 判断是不是回文串,复杂度 \(O(n^3)\)。
优化的暴力:枚举回文串中心对称点。然而,当回文串的长度是偶数时,这个对称点该如何表示呢?
考虑在 \(S\) 的首尾及每两个字母中间添加通配符 #
,这样,当回文串的长度是奇数时,对称点就在字母位置,反之在井号位置;如果我们发现了一个半径为 \(l\) 的回文串,它的真实长度就是 \(l-1\)。这里,回文半径指对称中心到串的一端的字符数(包括中心和端点),例如 #a#b#a#
半径为 \(4\),#a#b#b#a#
半径为 \(5\)。枚举中心点,再枚举一端,向两边扩展,复杂度 \(O(n^2)\)。
设在加工后的串 \(S'\) 中,以 \(i\) 为回文中心的最长回文子串半径为 \(p_i\),从 \(1\) 到 \(n\) 顺序枚举 \(i\),并考虑借助 \(p_{1\sim i-1}\) 得到 \(p_i\)。同时维护 \(mx,id\) 分别表示 \(1\sim i-1\) 中回文串的右端所能到达的最远下标,和能达到此的回文子串的中心。
考虑分类讨论:
容易求出 \(mx\) 关于 \(id\) 的对称点 \(mx'=id-(mx-id)=2\cdot id-mx\),同理可以求出 \(i\) 关于 \(id\) 的对称点 \(j\)。
当 \(i+p_j-1<mx\) 时:由于 \(i,j\) 都在 \(mx'\sim mx\) 范围内,所以绿色区域内的所有信息都是左右相似的,在当前情况下,可以得到 \(p_i=p_j\)。
当 \(i+p_j-1\ge mx\) 时:我们还需要考虑绿色区域之外的 \(i\) 的回文串的组成部分,这一部分暴力对称判断。
for k mx+1→n if s[k]==s[i*2-k] k←k+1 k←k-1; p[i]←k-i+1; mx←k id←i
暴力判断的过程才是真正占复杂度的,而暴力判断的过程其实是在将 \(mx\) 向右移。在整个过程中,\(mx\) 共移了 \(O(n)\) 次。
【模板】manacher 算法
#include<bits/stdc++.h> using namespace std; const int N=1.1e7+10; int p[N*2]; string str,s="#"; int main() { cin>>str; int n=str.length(); for(int i=0;i<n;i++)s+=str[i],s+='#'; n=s.length(); p[0]=1; int mx=0,id=0,ans=0; for(int i=1,j;i<n;i++){ if(mx>i&&mx>p[id*2-i]+i-1)p[i]=p[id*2-i]; else { for(j=mx+1;j<n&&s[j]==s[i*2-j];j++); j--; p[i]=j-i+1; } if(mx<i+p[i]-1)mx=i+p[i]-1,id=i; ans=max(ans,p[i]-1); } cout<<ans<<endl; }