manacher 学习笔记

这第一名到底要多强

不用问 一定有人向你挑战

到底还要过多少关

不用怕 告诉他们谁是男子汉

可不可以不要这个奖

不想问 我只想要流一点汗

我当我自己的裁判

不想说 选择对手跟要打的仗

-----周杰伦《三年二班》


manacher 学习笔记

放歌,以后一个学习笔记一首。以前的话,看心情补。

tmd,又要期末考了。痛苦,又要回去了。

低迷的状态,始终,都是黑暗的。

考虑这样一个问题,在一个字符串中,找到最长的回文子串。

对于一个字符串,它的对称中心就是这个字符串的中点,即关于这个位置两边的字符串完全相等(不包含该点)。

首先考虑,怎么判断某个字符串是一个回文串,不难发现,只用从他的对称中心往两边扩散,逐一匹配即可。所以就有了一个 O(n3)O(n^3) 的做法。

想到这里,就不难优化到 O(n2)O(n^2),考虑枚举可能的对称中心,往两边扩散即可。

但是,manacher 可以把他优化到 O(n)O(n)

首先要解决一个问题,对于一个长度为偶数的字符串的,其对称中心其实不能是某一个具体的下标,这时候,我们考虑在每两个字符中间位置添加一个 #,以及在首尾添加 #

  • 对于一个奇字符串,它有偶个空格 (n1)(n-1),再加上首尾两个 #,长度为奇数。

  • 对于一个偶字符串,它有奇的空格 (n1)(n-1),再加上首尾两个 #,长度为奇数。

至此,我们把原字符串所有的子串都变成长度为奇数的子串了,那么这样一来,其的对称中心,就有一个具体的下标。

继续引入,引入 radirad_i 表示以 ii 下标为回文中心的最长回文串半径,那么说明 S(iradi+1,i+radi1)S(i-rad_i+1,i+rad_i-1) 是以 ii 为中心的最长回文串。(注意该半径包含 ii

引入 RjR_j 表示 max1ji(j+radj1)\max_{1\le j \le i }(j+rad_j-1)CjC_j 表示能取到最大位置的 jj

考虑对这三者进行归纳求解,首先 rad1=R1=C1=1rad_1=R_1=C_1=1

然后大力分讨:

  • 如果 i>Ri1i>R_{i-1},以 ii 为对称中心开始扩散,暴力,时间复杂度为 O(RiRi1)O(R_i-R_{i-1}),(实际上是从 i>Rii -> R_i )。
  • 如果 i<Ri1i<R_{i-1},这个时候知道对着 Ci1C_{i-1} 对称过去,找到一个 j=(Ci1×2i)j=(C_{i-1}\times2-i),再进行分类:

如果 i+radjRj1i+rad_j\le R_{j-1},容易发现,radi=radjrad_i=rad_j,这是根据回文串的性质,因为 ii 在以 Ci1C_{i-1} 为中心,radCi1rad_{C_{i-1}} 为回文半径的回文串中,时间复杂度为 O(1)O(1)

如果 i+radj>Rj1i+rad_j>R_{j-1},看图,有一段是相等的(一直到 Rj1R_{j-1}),然后从 Rj1R_{j-1} 开始,继续往后拓展,一直到不能匹配位置,这里的时间复杂度实际上是 O(RiRi1)O(R_i-R_{i-1})

为什么对应的位置 jj 是最大的呢?因为根据回文串的特性,以 Ci1C_{i-1} 为中心,radCi1rad_{C_{i-1}} 为回文半径的这一回文串是固定的,且包含着 ii,所以就一定会完全重合,也就是已经确定下来的了。

至此,我们求出来了 radrad 数组,感觉他我们可以推一些东西。

首先,对于一个长度为 2k+12k+1 的字符串,它在原串的长度实际是 kk

对于一个中心 ii ,他在新串中最长回文串的长度是 radi×21rad_i\times2-1,这实际上在原串的长度,就是 radi1rad_i-1。能贡献的回文串数量,就是 radi/2\lfloor rad_i/2 \rfloor,这个可以推的。

关于时间复杂度,发现 RiR_i 始终单调不降,所以时间为 O(n)O(n)

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;
}

P4555

很容易想到应该要求以 ii 为结尾/开头的最长回文串,以开头举例。记这个数组为 pp

如果仅仅通过求 manacher 求的 rad 数组,不能保证可以覆盖到全部的点,但是我们先把他标记到 pp 去。

用归纳法去想,如果现在已经知道了 i1i-1 的答案,那么一种就是从 pi1p_{i-1} 直接过来,另一种就是 ii 这里本身就被标记答案了。+2+2 原因是

pi=max(pi1+2,pi)p_i=\max(p_{i-1}+2,p_i)

结尾的递推方式和开头一样,相当于把字符串翻转求开头。

然后算算答案即可。

CF1080E

考察字符串的本质看出来了,只会一个 O(n4×26)O(n^4\times26) 的,但是能搞出来这样一道题,我觉得是有进步的。

至少除了正解对manacher的优化以外,其他都看出来了。

先考虑如果打乱字符怎么判断能否构成回文串,其实发现回文串的本质就是确定一个回文中心,然后不断往外扩展,所以只用统计 2626 个字符出现的次数,按照奇偶分类即可。

继续想,如果这样一个子矩阵是合法的话,必然能发现这一个子矩阵每行都是回文串,且是沿着打横的一条对称轴对称过去的。

(自己的做法是枚举打横的对称中心,然后上下扩展暴力找。)

所以我们不妨对这个子矩阵打横的字符串赋值(如果两个字符串的值相等,说打横的串重排成回文串后完全重合),(也就是把他们抽象成点)这时候,就只是相当于做打竖的找回文串了。

那我们就不妨枚举这个矩阵左右边界(所在的直线),把这 nn 个字符串都抽象成点,然后跑manacher,跑出来的回文串就满足打竖,打横,都是回文串了。

怎么表示两个打横的串的相对关系呢?这是一个Trick。可以哈希,对 az 依次对应的值记为 p0,p1,p2...p25p^0,p^1,p^2...p^{25},然后遇到一个字符加上对应的 pp 值,如果总和相等,就表示两个字符串是能够重排后完全重合的。

如果一个字符串无论如何重排都不能构成回文串呢?那直接用 1,2,3...-1,-2,-3... 标记即可。这也是一个Trick。

然后就是实现细节的问题,记得乘法要 long long,底数 1000710007,模数 1e9+71e9+7

CF17E

前前后后做了很久。期末考前就开始搞了,蚌。

首先第一点没有想到,正难则反,很多题其实都可以适当的套一下这个想法,可能会有提供一些解题思路上的帮助。

所以我们考虑统计不相交的对数,用总数减去就是答案。

后面都是自己想的。

那既然是算贡献,那就套路的考虑如果当前有一个回文串 S(l,r)S(l,r) 了,怎么统计答案。

不难发现,这个答案就是从 [r+1,n][r+1,n] 开头的回文串的总数,至于在 ll 左边和他不相交的回文串,由于我们是从左往右算的,这里就不用重复计算了。

计算从一个位置 ii 开始的回文串数量用差分即可,记这个数组为 cc。(即每次在求解完 radirad_i 后,对 iradi+1i-rad_i+1i+1i+1 修改即可,差分能解决静态区间加类似的问题。但查询要 O(n)O(n)。)

这里因为是一段连续区间的和,对 cc 做前缀和记为 sss[n]s[r]s[n]-s[r] 就是当前的贡献。

然后再考虑拓展,就是计算 S(iradi+1,i+radi1)S(i-rad_i+1,i+rad_i-1) 中所有回文串的贡献,不妨把他写成和的形式,就发现他还是对连续区间求和,又要对 ss 做前缀和记为 s1s1

然后剩下就是推式子的事情了,静下心了搞就可以。这里主要是分奇数和偶数进行一个讨论求和。

然后主要就是细节问题,记得开ll。

这个题主要给我们的启发就是,从一个字符串考虑-->进行拓展。求出来的 radrad 数组可以拍回到原串去解决很多问题,这是从原串的角度考虑的。

tmd,CF UKE。

posted @   June_Failure  阅读(2)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示