manacher 学习笔记
这第一名到底要多强
不用问 一定有人向你挑战
到底还要过多少关
不用怕 告诉他们谁是男子汉
可不可以不要这个奖
不想问 我只想要流一点汗
我当我自己的裁判
不想说 选择对手跟要打的仗
-----周杰伦《三年二班》
manacher 学习笔记
放歌,以后一个学习笔记一首。以前的话,看心情补。
tmd,又要期末考了。痛苦,又要回去了。
低迷的状态,始终,都是黑暗的。
考虑这样一个问题,在一个字符串中,找到最长的回文子串。
对于一个字符串,它的对称中心就是这个字符串的中点,即关于这个位置两边的字符串完全相等(不包含该点)。
首先考虑,怎么判断某个字符串是一个回文串,不难发现,只用从他的对称中心往两边扩散,逐一匹配即可。所以就有了一个 的做法。
想到这里,就不难优化到 ,考虑枚举可能的对称中心,往两边扩散即可。
但是,manacher 可以把他优化到 。
首先要解决一个问题,对于一个长度为偶数的字符串的,其对称中心其实不能是某一个具体的下标,这时候,我们考虑在每两个字符中间位置添加一个 #
,以及在首尾添加 #
。
-
对于一个奇字符串,它有偶个空格 ,再加上首尾两个
#
,长度为奇数。 -
对于一个偶字符串,它有奇的空格 ,再加上首尾两个
#
,长度为奇数。
至此,我们把原字符串所有的子串都变成长度为奇数的子串了,那么这样一来,其的对称中心,就有一个具体的下标。
继续引入,引入 表示以 下标为回文中心的最长回文串半径,那么说明 是以 为中心的最长回文串。(注意该半径包含 )
引入 表示 , 表示能取到最大位置的 。
考虑对这三者进行归纳求解,首先 。
然后大力分讨:
- 如果 ,以 为对称中心开始扩散,暴力,时间复杂度为 ,(实际上是从 )。
- 如果 ,这个时候知道对着 对称过去,找到一个 ,再进行分类:
如果 ,容易发现,,这是根据回文串的性质,因为 在以 为中心, 为回文半径的回文串中,时间复杂度为 。
如果 ,看图,有一段是相等的(一直到 ),然后从 开始,继续往后拓展,一直到不能匹配位置,这里的时间复杂度实际上是 。
为什么对应的位置 是最大的呢?因为根据回文串的特性,以 为中心, 为回文半径的这一回文串是固定的,且包含着 ,所以就一定会完全重合,也就是已经确定下来的了。
至此,我们求出来了 数组,感觉他我们可以推一些东西。
首先,对于一个长度为 的字符串,它在原串的长度实际是 。
对于一个中心 ,他在新串中最长回文串的长度是 ,这实际上在原串的长度,就是 。能贡献的回文串数量,就是 ,这个可以推的。
关于时间复杂度,发现 始终单调不降,所以时间为 。
int Manacher(){
n=strlen(s+1);
int ans=0;
for(int i=0;i<=n;i++) S[i*2+1]='#';
for(int i=1;i<=n;i++) S[i*2]=s[i];
n=2*n+1;
rad[1]=R=C=1;
for(int i=2;i<=n;i++){
if(i<=R) rad[i]=min(rad[2*C-i],R-i);
while(S[i-rad[i]]==S[i+rad[i]]&&i-rad[i]>=1&&i+rad[i]<=n)
++rad[i];
if(i+rad[i]-1>R) C=i,R=i+rad[i]-1;
ans=max(rad[i]-1,ans);
}
return ans;
}
很容易想到应该要求以 为结尾/开头的最长回文串,以开头举例。记这个数组为 。
如果仅仅通过求 manacher
求的 rad
数组,不能保证可以覆盖到全部的点,但是我们先把他标记到 去。
用归纳法去想,如果现在已经知道了 的答案,那么一种就是从 直接过来,另一种就是 这里本身就被标记答案了。 原因是
结尾的递推方式和开头一样,相当于把字符串翻转求开头。
然后算算答案即可。
考察字符串的本质看出来了,只会一个 的,但是能搞出来这样一道题,我觉得是有进步的。
至少除了正解对manacher的优化以外,其他都看出来了。
先考虑如果打乱字符怎么判断能否构成回文串,其实发现回文串的本质就是确定一个回文中心,然后不断往外扩展,所以只用统计 个字符出现的次数,按照奇偶分类即可。
继续想,如果这样一个子矩阵是合法的话,必然能发现这一个子矩阵每行都是回文串,且是沿着打横的一条对称轴对称过去的。
(自己的做法是枚举打横的对称中心,然后上下扩展暴力找。)
所以我们不妨对这个子矩阵打横的字符串赋值(如果两个字符串的值相等,说打横的串重排成回文串后完全重合),(也就是把他们抽象成点)这时候,就只是相当于做打竖的找回文串了。
那我们就不妨枚举这个矩阵左右边界(所在的直线),把这 个字符串都抽象成点,然后跑manacher,跑出来的回文串就满足打竖,打横,都是回文串了。
怎么表示两个打横的串的相对关系呢?这是一个Trick。可以哈希,对 a
到 z
依次对应的值记为 ,然后遇到一个字符加上对应的 值,如果总和相等,就表示两个字符串是能够重排后完全重合的。
如果一个字符串无论如何重排都不能构成回文串呢?那直接用 标记即可。这也是一个Trick。
然后就是实现细节的问题,记得乘法要 long long
,底数 ,模数 。
前前后后做了很久。期末考前就开始搞了,蚌。
首先第一点没有想到,正难则反,很多题其实都可以适当的套一下这个想法,可能会有提供一些解题思路上的帮助。
所以我们考虑统计不相交的对数,用总数减去就是答案。
后面都是自己想的。
那既然是算贡献,那就套路的考虑如果当前有一个回文串 了,怎么统计答案。
不难发现,这个答案就是从 开头的回文串的总数,至于在 左边和他不相交的回文串,由于我们是从左往右算的,这里就不用重复计算了。
计算从一个位置 开始的回文串数量用差分即可,记这个数组为 。(即每次在求解完 后,对 和 修改即可,差分能解决静态区间加类似的问题。但查询要 。)
这里因为是一段连续区间的和,对 做前缀和记为 , 就是当前的贡献。
然后再考虑拓展,就是计算 中所有回文串的贡献,不妨把他写成和的形式,就发现他还是对连续区间求和,又要对 做前缀和记为 。
然后剩下就是推式子的事情了,静下心了搞就可以。这里主要是分奇数和偶数进行一个讨论求和。
然后主要就是细节问题,记得开ll。
这个题主要给我们的启发就是,从一个字符串考虑-->进行拓展。求出来的 数组可以拍回到原串去解决很多问题,这是从原串的角度考虑的。
tmd,CF UKE。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现