今天讲的字符串哈希题觉得都很水啊,咋一听到 Manacher 题就懵逼了呢?重学一遍……

简介

给定一个长度为 nn 的字符串 SS,找到所有回文子串。
最坏情况下有 O(n2)O(n^2) 个回文串,为了做到线性,我们使用回文半径来表示回文。这样,我们要求的就转化为:对于每个位置 ii,求出最长回文半径。
使用字符串哈希可以容易地在 O(nlogn)O(n\log n) 时间内解决,但 Manacher 更为简洁和高效。

实现过程

约定:[l,r][l,r] 表示下标在 l,rl,r 之间的子串。
我们只考虑长度为奇数的回文串,可以通过在字符串每个字符间加入无关字符如 # 等来把长度为偶数的串转化为长度为奇数的串。接下来,我们维护 pip_i 表示以 ii 为重心的回文半径,rr 为当前找到的覆盖位置最靠右的回文串能覆盖到的最大下标,midmid 表示它的回文中心的下标。
计算过程考虑分类讨论:

  • i>ri>r 时,直接暴力一步步向后跳。
  • iri\le r 时,设 jjii 关于 midmid 的对称点,即 mid×2imid\times 2-i
    由于 [jpj,j+pj][j-p_j,j+p_j][ipi,i+pi][i-p_i,i+p_i] 相等且都是回文串,所以 pip_i 可以直接取 pjp_j 的值。但这个推论成立当且仅当 i+pjri+p_j\le r,否则 pip_i 只能取到 ri+1r-i+1,然后再暴力扩展。
    综上,pi=min(pj,ri+1)p_i=\min(p_j,r-i+1)

rr 只能向右移动,故最多移动 O(n)O(n) 次。其余情况的计算显然是 O(1)O(1) 的。

1
2
3
4
5
6
7
8
9
10
for(int i=1,r=0,mid=0;i<=n;i++){
int j=2*mid-i;
if(i<=r) p[i]=min(r-i+1,p[j]);
else p[i]=1;
while(s[i-p[i]]==s[i+p[i]]) ++p[i];
if(i+p[i]-1>r){
r=i+p[i]-1;
mid=i;
}
}

部分引自 https://oi-wiki.org/string/manacher/

非常感谢这篇博客,讲的很透彻,帮我彻底弄懂了 Manacher。
https://www.luogu.com.cn/article/2fmdma9m