Manacher求字符串中回文串的个数

思路

引入

一些暴力

\(Θ(𝑛)\) 枚举左端点, \(Θ(𝑛)\) 枚举右端点,然后暴力判断,复杂度 \(Θ(𝑛^3)\)

当然,可以优化,判断那部分用哈希搞一搞以后就是 \(Θ(𝑛^2)\) 了。

另一个暴力

\(Θ(𝑛)\) 枚举对称中心,暴力往两边拓展,最坏复杂度 \(Θ(𝑛^2)\)

这就是 \(\operatorname{Manacher}\) 算法的基础。

优化

我们发现,每遍历到一个新的对称中心,都需要再向两边拓一遍,然而这就必然会浪费之前花费大量时间求出来的信息,所以要考虑用之前的信息来转移该点的答案。

由于之前求的信息均与对称相关,我们考虑使用有关对称的信息来更新答案。

怎么使用呢?我们可以将这个点对称到已经求过的地方来统计答案,而为了尽可能利用更多的信息,选择的点 \(id\) 应满足:

{[(以该点为对称中心的回文串)的右端点]在所有[(以「已求点」为对称中心的回文串)的右端点]中是最靠右的}

将以该点位对称中心的回文串称为 \(s\)

不难发现,所求点 \(i\) 一定在 \(s\) 串的右侧,因为 \(s\) 串左侧的所有点必定已经遍历过,不过 \(s\) 可能无法覆盖到 \(i\) 点,这里先假设 \(s\) 能够覆盖到 \(i\)

然后把 \(i\) 点对称到 \(s\) 串左侧对应位置(称为 \(j\) ),如下图所示:

显然,以 \(j\) 为对称中心的回文串本来是 \(ab\) ,但 \(s\) 串右侧不一定有对应的 \(ac\) 串,所以只能舍弃,同理 \(bd\) 段也要丢弃。所以最后产生贡献的只有 \(cd\) ,对应 \(i\) 周围的 \(ef\) 。自然地,如果 \(ab\) 在对称半径内,整个 \(ab\) 都可以对 \(i\) 产生贡献。

也就是说设 \(f_i\)\(i\) 的回文半径,$$f_i=\min(f_{id+id-i},l-i)$$

但是,不能保证 \(s\) 串外没有对应的回文串,也就是说 \(i\) 的回文半径可能更长,还需要继续暴力向两边拓展。

其次,如果 \(s\) 串没有覆盖到 \(i\) 点,那么我们只能采用完全暴力的方式计算答案。

特别地,建议将最右端与 \(i\) 点重合也算为没有覆盖到,因为这样可以少取一个 \(\min\)

复杂度 \(Θ(𝑛)\) ,因为保 \(s\) 串最右端单调递增,所以是 \(Θ(𝑛)\) 的。

细节

特殊处理

对于奇回文,对称中心十分好找,但偶回文没有单独的对称中心,处理起来十分麻烦,同时在拓展到最边缘的两个无关字符以后,暴力程序还会继续尝试拓展,进而导致越界。

于是,我们可以采用下面的办法巧妙的解决这些问题。

  • 在字符串首尾及每个字符间都插入一个 #

  • 在首尾两端各插入两个不同的符号(如$&),用于判断是否越界

例如偶回文abcddcba转化后变成了奇回文&#a#b#c#d#d#c#b#a#$

统计答案

显然,所加的无关字符都是一一对应的,所以$$实际回文半径长度=\left \lfloor \frac{求出的回文半径长度}{2} \right \rfloor$$

实际上,回文半径长度就是回文串的个数,对于每一个对称中心求和就好

Code

点击查看代码
#include <cstdio>
#include <cstring>
#include <algorithm>
char s[11000005], use_s[33000005]; // 所使用的字符串 
int padius[33000005]; // 回文半径 
inline int get_len() { // 预处理 
	int len = strlen(s), // 原串长度 
	cnt = 0; // 用于标记 
	use_s[cnt++] = '&'; // 防止越界 
	use_s[cnt++] = '#'; // 先标记第一个 
	for (int i = 0; i < len; i++) {
		use_s[cnt++] = s[i];
		use_s[cnt++] = '#';
	} // 在每次循环中,先存储该字符,再标记# 
	use_s[cnt] = '$'; // 仍然是防止越界 
	return cnt; // 返回串的长度 
}
inline int Manacher() {
	int ans = 0, // 最终答案 
	len = get_len(), // 所使用的串的长度 
	rs = 0, id = 0; // 最右端和对应的对称中心 
	for (int i = 1; i < len; i++) {
		if (i < rs) padius[i] = std::min(padius[id + (id - i)], rs - i);
		else padius[i] = 1; // 同理,不过是初始化 
		while (use_s[i + padius[i]] == use_s[i - padius[i]]) padius[i]++; // 暴力拓展 
		if (i + padius[i] > rs) rs = i + padius[i], id = i; // 更新id 
		ans += (padius[i] >> 1);
	}
	return ans;
}
int main()
{
	scanf("%s", s);
	printf("%d\n", Manacher());
	return 0;
}
posted @ 2021-12-18 15:41  Tritons  阅读(225)  评论(0)    收藏  举报
页脚Html代码: