Manacher算法小记
一、算法简介
Manacher 算法能够在线性的时间内求出以每个位置为中心的最长回文半径。
首先将字符串所有字符(包括头尾)插入相同的分隔符,因为 Manacher 仅能找到长度为奇数的回文串,并在整个串前后方插入另两种分隔符防止越界。
定义位置 \(i\) 的回文半径为以 \(i\) 为对称中心的所有长度为奇数的回文串 \(S[l:r]\) 两端与 \(i\) 的距离 \(+1\)。显然,若 \(x\) 是 \(i\) 的回文半径,则 \(S[i-x+1:i+x-1]\) 是回文串,且小于 \(x\) 的正整数均是 \(i\) 的回文半径。设 \(p_i\) 表示 \(i\) 的最长回文半径。
Manacher 算法记录在所有遍历过的位置 \(1\sim i-1\) 中,以任意一个点为回文中心的右端点的最大值 \(r\),即 \(r=\max\limits_{j=1}^{i-1}\{j+p_j-1\}\)。设 \(d\) 为取到这个最大值的对称中心。
对于当前位置 \(i\),若 \(i>r\),则直接暴力求 \(p_i\)。否则 \(i\leq r\),先将 \(i\) 赋值为 \(\min(r-i+1,p_{2d-i})\),再逐位扩展。为什么是这样?因为 \(i\) 与 \(2d-i\) 关于 \(d\) 对称,所以在 \([d-p_d+1,d+p_d-1]\) 范围内,以 \(2d-i\) 为对称中心的回文串也是以 \(i\) 为对称中心的回文串。若 \(p_{2d-i}<r-i+1\),则 \(p_i\) 最终就会等于 \(p_{2d-i}\),否则 \(p_{2d-i}\) 还可以更大,矛盾。若 \(p_{2d-i}\geq r-i+1\),则 \(p_i\) 被初始化为 \(r-i+1\),使得每次扩展都会将 \(r\) 向右移动 \(1\)。故时间复杂度均摊线性。
P3805 【模板】manacher 代码如下
int main()
{
scanf("%s",s+1); n=strlen(s+1);
t[0]='#'; t[++m]='@';
for(int i=1; i<=n; i++)
t[++m]=s[i],t[++m]='@';
t[++m]='!';
int d=0,r=0;
for(int i=1; i<m; i++)
{
if(i>r)
p[i]=1;
else
p[i]=min(r-i+1,p[2*d-i]);
while(t[i-p[i]]==t[i+p[i]])
p[i]++;
if(i+p[i]-1>r)
r=i+p[i]-1,d=i,cmax(ans,p[i]-1);
}
printf("%d\n",ans);
return 0;
}
二、一些习题
P4555 [国家集训队] 最长双回文串
我的想法是枚举第一个串 \(X\) 的对称中心 \(i\),查询所有 \(j-p_j+1\leq i+p_i-1\) 的 \(j\) 与 \(i\) 能构成的串的最大值,树状数组维护即可,注意要判断两个串是否长度都大于 \(0\)。
另一种做法是用 Manacher 预处理出以 \(i\) 为结尾/开头的最长回文串长度 \(x_i,y_i\),答案即 \(\max\{x_i+y_{i+1}\}\),时间复杂度线性 。
P1659 [国家集训队] 拉拉队排练
又被我写复杂了……
因为只要求奇数,我们只考虑以原串的点为中心的回文串的贡献,设当前点为 \(i\),记 \(x_i=p_i/2\),则它可以产生长度为 \(2\times y-1\) 的回文串,其中 \(y\in [1,x_i]\)。我用线段树维护这个区间加的操作。但其实你直接差分一下,在 \(p_i-1\) 的位置单点加。最后统计答案的时候遇到长度为偶数的直接跳过即可,就不用再考虑除以 \(2\) 什么的了。
P6216 回文匹配
因为模拟赛考了这题发现我不会 Manacher 于是才有了这篇博客
要求回文串长度为奇数,容易想到用 Manacher 计算出以每个点为对称中心的回文串。再来计算 \(s_2\) 的贡献。可以先用 KMP 跑出 \(s_2\) 在 \(s_1\) 中出现的所有位置。
现在对于一个回文串 \(s_1[l:r]\),设它的对称中心为 \(mid\)。假设 \(s_1[i:j]=s_2\,(l\leq i\leq j\leq r)\),则 \(s_1[i:j]\) 对所有以 \(mid\) 为对称中心的回文串的贡献为 \(\min(i-l+1,r-j+1)\)。这个 \(\min\) 很烦,于是我们直接主定 \(s_2\) 的中心在 \(mid\) 左边,这样贡献就是 \(i-l+1\) 了。记 \(L\) 为满足 \(s_2\) 对称中心在左边的 \(s_2\) 最大左端点,直接将 \(-l\) 打到区间 \([l,L]\) 上,最后统计答案即可。对于 \(s_2\) 中心等于 \(mid\) 或者在 \(mid\) 右边的同理。
差分一下时间复杂度线性。
P5446 [THUPC2018] 绿绿和串串
因为只要求长度在 \(|S|\) 以内的,所以第一次翻转的断点(即最后一个字符)一定在 \(S[1:n]\) 以内,其中 \(n=|s|\)。
\(n\) 肯定是一种合法的方案。对于断点 \(i\),若它的最长回文半径向右能到 \(n\),那它肯定可以。若它的最长回文半径向左能到 \(1\),那么只要向右到达的那个点可以那 \(i\) 也可以。时间复杂度线性。

浙公网安备 33010602011771号