Manacher 学习笔记

P3805 【模板】Manacher

思路

使用时间复杂度 \(O(n^3)\) 的代码显然是无法通过的,需要效率更高的代码。

这里,我们需要用到一种manacher 算法,它有一个用途:\(O(n)\) 的线性时间复杂度内找到一个字符串的最长回文子串的长度。

首先,我们要将每个字符之间和字符串的两端插入 # 字符,从而使这个字符串变为奇回文串,且在下标为 \(0\) 的位置插入哨兵 $,方便统一处理。

  • 奇回文串:abcba 变为 $#a#b#c#b#a#
  • 偶回文串:abba 变为 $#a#b#b#a#

接着,我们引入一个概念:回文半径 \(d\) 函数

回文半径是指以 \(i\) 为中心的最长回文串长度的一半,向上取整

name value
\(str\) # a # b # a # b #
\(i\) 1,2,3,4,5,6,7,8,9
\(d_i\) 1,2,1,4,1,4,1,2,1

一个字符串的最长回文子串的长度,就是改造串的最长回文半径减 \(1\)

知道了最长回文子串的长度的求法,我们就来看看怎样求回文半径 \(d\) 函数。

再来学习一个概念:加速盒子,区间 \([l,r]\)

在盒内(即 \(i \leq r\)),我们可以借助回文子串的对称点\(d_i\) 进行直接转移,例如 \(i=5\),此时 \(l=1\)\(r=7\),中心为 \(4\),我们发现,它的对称点就是 \(r-i+l\)

接下来分情况讨论:

  • \(d_{r-i+l}<r-i+1\),则进行转移:\(d_i \leftarrow d_{i-l+1}\)
  • \(d_{r-i+l} \geq r-i+1\),则 \(d_i \leftarrow r-i+1\),开始从 \(r+1\) 枚举。

在盒外(即 \(i>r\)),则从 \(i\) 开始枚举。


求出 \(d_i\) 后,若 \(i+d_i-1>r\),则修改区间左右端点:

  • \(l=i-d_i+1\)
  • \(r=i+d_i-1\)

代码

AC Code
#include <bits/stdc++.h>
using namespace std;
char a[30000000],s[30000000];
int n,k,l,r,d[30000000],maxn;
// manacher的实现
void manacher(char *s,int n)
{
	// 先初始化d[1]和r,此处r取0或1都可以,只要保证第一个if不执行
	d[1]=1;
	r=1;
	for(int i=2;i<=n;i++)
	{
		// 判断是否在盒内
		if(i<=r)
		{
			// 将d[i]进行转移
			d[i]=min(d[r-i+l],r-i+1);
		}
		// 判断对称点是否相等,相等便继续枚举
		while(s[i-d[i]]==s[i+d[i]])
		{
			d[i]++;
		}
		// 修改区间左右端点 
		if(i+d[i]-1>r)
		{
			l=i-d[i]+1;
			r=i+d[i]-1;
		}
		// 找到改造串最长回文半径-1,即最长回文子串的长度
		maxn=max(maxn,d[i]-1);
	}
}
int main()
{
	// 从下标为1的位置开始,输入原字符串a
	scanf("%s",a+1);
	n=strlen(a+1);
	// 构建改造奇回文串
	s[0]='$';
	s[++k]='#';
	for(int i=1;i<=n;i++)
	{
		s[++k]=a[i];
		s[++k]='#';
	}
	// manacher
	manacher(s,k);
	cout << maxn;
	return 0;
}
posted @ 2026-05-16 11:39  kevin1426730  阅读(2)  评论(0)    收藏  举报