Manacher
Manacher 是一种 \(O(n)\) 的回文串查找方式.
朴素算法
长度为 \(n\) 的字符串最多有 \(n^2\) 个回文子串. 朴素算法枚举回文串的中心。朴素算法复杂度为 \(O(n^2)\) .
int find(string s){for (int i=0;i<s.size();i++){d1[i]=1;while(0<=i-d1[i] && i+d1[i]<s.size() && s[i-d1[i]]==s[i+d1[i]]){d1[i]++;}d2[i]=0;while(0<=i-d2[i]-1 && i+d2[i]<s.size() && s[i-d2[i]-1]==s[i+d2[i]]){d2[i]++;}}
}
其中 \(d1_i\) 表示以 \(i\) 为中心的奇回文串的数量, \(d2_i\) 表示以 \(i-1,i\) 为中心的偶回文串的数量。
Manacher算法
Manacher算法的核心在于记录一个最靠右的已知回文串,再枚举回文串右侧的中心时,可以直接继承回文串左侧的中心的数据。
图1:
\(1,\overbrace{2,\underbrace{3,4,5,6,7,8,9}_{\text{回文串,大小为7}},10,\underbrace{11,12,13,14,15,16,17}_{\text{继承前文的回文串,大小最大为7}},18}^{\text{回文串}},19,20\)
因为右侧回文串在大回文串内,所以与左侧对称,无法再延伸。
图2:
\(1,\overbrace{\underbrace{2,3,4,5,6,7,8,9}_{\text{回文串,大小为8}},10,\underbrace{11,12,13,14,15,16,17,18}_{\text{继承前文的回文串,大小最小为8}}}^{\text{回文串}},19,20\)
虽然右侧回文串在大回文串下,但是右端点可以延伸到大回文串之外,可能 \(S_2 \not = S_{19}\) ,所以可以再延伸。
实现细节
为了快速计算,我们维护已找到的最靠右的子回文串的 边界 \((l,r)\)。初始时,我们设 $l=1 $ 和 \(r=-1\)。
现在假设我们要对下一个中心 \(i\) 计算 \(d_i\) ,而之前所有 \(d_{i-1}\) 中的值已计算完毕。我们将通过下列方式计算:
-
如果 \(i\) 位于当前子回文串之外,即 \(i>r\),那么我们调用朴素算法。然后更新最右侧区间 \((l,r)\) 。
-
对于 \(i \le r\) 的情况。首先在子回文串 \((l, r)\) 中反转位置 \(i\),即我们得到 \(j=l+(r-i)\) 。我们从已计算过的 \(d_j\) 的值中获取回文串大小。因为位置 \(j\) 同位置 \(i\) 对称,我们可以置 \(d_i=d_j\) 。
-
然而对于图二的情况:当「内部」的回文串到达「外部」回文串的边界时,即 \(j-d_j+1 \le l\) 。因为在「外部」回文串范围以外的对称性没有保证,因此直接置 \(d_i=d_j\) 将是不正确的。为了正确处理这种情况,我们应该置 \(d_i=r-i\)。之后我们将运行朴素算法以尝试尽可能增加 \(d_i\) 的值。最后更新最右侧区间 \((l,r)\) 。
Manacher复杂度
考虑到每次执行朴素算法都会更新右端点 \(r\) ,所以实际算法复杂度为 \(O(n)\) 。
代码:
vector<int> d1(n);
int l=0,r=-1;
for(int i=0;i<n;i++){if(i>r) k=1;else k=min(d1[l+r-i],r-i+1);while(0<=i-k && i+k<n && s[i-k]==s[i+k]){k++;}d1[i]=k--;if(i+k>r){l=i-k;r=i+k;}
}