Manacher能对物体施加“力”……
Manacher是一种字符串匹配算法。此算法的核心在于\(rds\)数组以及它的求法。
(Manacher算法与Z算法高度相似,因此本blog高仿Z算法)
定义\(rds\)数组:\(rds_{a,i}\)表示以字符串\(a\)的第\(i\)位为中心,最多能往两边各扩展多少,使得覆盖过的是一个回文串,即\(rds_{a,i}=\max\limits_{a_{i-j+1\sim i+j-1}=a_{i-j+1\sim i+j-1}^\mathrm r}\{j\}\)。例如若\(a=\texttt{abacabac}\),那么\(rds_a=[1,2,1,4,1,3,1,1]\)。
给定字符串\(a\),现在我们需要求出\(rds_a\)。
假设我们现在已经知道了\(rds_{a,1\sim i-1}\)和使得覆盖过的回文子串的右端点\(r=mid+rds_{a,mid}-1\)最大的回文中心\(mid\)和右端点\(r\),要求出\(rds_{a,i}\)并更新\(mid,r\),那么分\(2\)种情况:
这样按上述方法从\(i=1\)递推到\(i=|a|\),便可求出\(rds_a\)数组。
下面是求\(rds\)数组的代码:
//|a|=n
void manacher(){求rds数组
int mid=0,r=0;//使得右端点最大的回文中心和右端点
for(int i=1;i<=n;i++)//从i=1递推到i=n
if(r<i){//第1种情况
rds[i]=0;
while(i-rds[i]>=1&&i+rds[i]<=n&&a[i-rds[i]]==a[i+rds[i]])rds[i]++;//直接向两边暴力匹配
mid=i;r=i+rds[i]-1;//更新右端点最大的回文中心和右端点
}
else if(i+rds[2*mid-i]<=r)rds[i]=rds[2*mid-i];//第2种情况的第1种情况
else{//第2种情况的第2种情况
rds[i]=r-i+1;//rds[i]至少有r-i+1这么多
while(i-rds[i]>=1&&i+rds[i]<=n&&a[i-rds[i]]==a[i+rds[i]])rds[i]++;//后面再暴力匹配
mid=i;r=i+rds[i]-1;//更新右端点最大的回文中心和右端点
}
}
按上述方法求\(rds\)数组的时间复杂度是线性的\(\mathrm{O}(|a|)\)。
证明(感性):观察上述方法可发现,只有当\(i>r\)时,才可能将这个位置的字符作为在当前回文中心右边的那个字符与左边关于回文中心对称的字符匹配,而匹配结束后会把\(r\)更新至最后一个匹配成功的位置,所以每个字符最多会作为在当前回文中心右边的那个字符与左边关于回文中心对称的字符成功匹配\(1\)次,所以匹配成功的总次数为\(\mathrm{O}(|a|)\);算\(rds_{a,i}\)时,如果往后暴力匹配(即遇到的不是第\(2\)种情况的第\(1\)种情况),那么第\(1\)次匹配失败就会停下来,所以匹配失败的总次数也为\(\mathrm{O}(|a|)\)。因此总时间就是匹配所花的时间\(\mathrm{O}(|a|)+\mathrm{O}(|a|)=\mathrm O(|a|)\)再加上一些赋值、更新\(mid,r\)等一些\(1\)次只要\(\mathrm O(1)\)的操作,就还是\(\mathrm O(|a|)\)了。得证。
\(rds\)数组可以通过对每个回文中心二分+哈希共\(\mathrm O(|a|\log_2|a|)\)求出。显然,Manacher复杂度更优。
接下来是\(2\)个经典的应用:
给定字符串\(a,|a|=n\),求\(a\)的最长回文子串。显然,回文子串分成\(2\)类:
然鹅还是需要调整调整。显然,长度为奇数的回文子串也可以用跟偶数一样的方法算,这样只需要跑一次Manacher。不难发现,当以\(b\)的某个字符的位置为回文中心的最长回文子串抵到了\(b_1\)或\(b_{|b|}\),那么两端不是\(\texttt!\),否则是\(\texttt!\)。\(2\)种情况,给将\(b\)中回文子串的长度还原成\(a\)中所对应的回文子串的长度造成了麻烦。不妨在\(b\)两端再加一个\(\texttt!\),即令\(b=\sum\limits_{i=1}^{n}(\texttt!+a_i)+\texttt!\),这样以\(b\)的某个字符的位置为回文中心的最长回文子串两端一定是\(\texttt!\)了。此时\(b\)中的最长回文半径\(rds_{b,i}\)所对应的\(a\)中的回文子串的长度为\(rds_{b,i}-1\),于是答案为\(\max_{i=1}^{|b|}\{rds_i-1\}\);
给定字符串\(a,|a|=n\),求\(a\)的非空回文子串个数。与上面那个问题类似,令\(b=\sum\limits_{i=1}^{n}(\texttt!+a_i)+\texttt!\),那么答案为\(\sum\limits_{i=1}^{|b|}\left\lfloor\dfrac{rds_{b,i}}2\right\rfloor\)。
原文:https://www.cnblogs.com/ycx-akioi/p/Manacher-algorithm.html