Manacher算法实现

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;
	}
}
posted @ 2025-08-17 15:07  allenyuan9038074  阅读(28)  评论(0)    收藏  举报