manacher 学习笔记

好久之前在洛谷上写了一篇,但是之前迁移博客园的时候忘记迁这篇过来了,看了看写的也就那样,还是自己再写一篇,相当于重学一遍罢了。

令字符集为 \(S\)\(s_0\) 为原串。

首先是 manacher 一个最著名的思路:在 \(s_0\) 的开头结尾和每个字符中间加入一个不在 \(S\) 中的字符,以减少分类讨论。

令该字符为 ''/'',修改后字符串为 \(s\)

这样做的原理实际上是将 \(s_0\) 中的长度为偶数的回文串强行在中间加上了分隔符,这样在不影响奇数长度回文串的回文中心的前提下,让偶数长度回文串拥有了一个确切的回文中心。

翻译成人话就是所有的回文串都变成奇数长度的了,所以不需要分类讨论奇偶了。

我觉得这个思路很厉害,有了这个你写朴素算法都方便,这也是我即使过了很久都没碰它,已经记不住别的操作,但是还能记住这个思路的原因。

现在只需要考虑奇数长度回文串怎么处理了。

令当前位置为 \(i\),以第 \(i\) 位为回文中心的回文串的半径为 \(r_i\)\(i\) 以前的 \(r_i\) 都已经处理好了。

我们知道,在一个回文串中,左部与右部是对称的,那么有一个理所当然的想法就是:能不能通过已经被处理好的左部来更新尚未处理的右部。

显然是可以的,只要知道 \(i\) 处于哪个回文串就行。

为了不会那么慢同时又满足正确性,我们肯定是选取第一个能覆盖到最远右端点 \(mr\),的回文串,因为在满足尽可能的使每个点都可以使用已知信息的前提下,这样的串是最长的。

那有没有一种可能,我明明还没超过 \(mr\),有着更长的串可以用,却因为一个更短的串更新了 \(mr\) 导致不能用以前的信息了,而用以前的信息更新会更优呢?

当然是不存在的,原因是回文信息是可传递的,如果你用以前的信息更新,那么你用新的信息更新是一样甚至更优的,你虽然由当前的回文串的左部进行更新,但是这个左部也是由以前的它的左部更新的,而且使用了后面的信息,这对当前位置是有利的,所以用新的信息更新是不劣的。

当然使用左部更新也是不完全的,如果更新的长度越过了 \(mr\),那么这样的信息是不保证正确性的,因为后面的你不知道,同样的,左部更新的也有可能不够,这时候就接着暴力检查更新就可以了。

时间复杂度入 \(O(n)\),但因为我比较菜,所以不会证明复杂度。

string mk_s(string s0)
{
	string s;
	for (auto c : s0)
	{
		s.push_back(c);
		s.push_back('/');
	}
	return s;
}
vector<int> manacher(string s0)
{
	string s = mk_s("-" + s0);
	int n = s.size() - 1, mr = -1, mid = 0;
	vector<int> vec(1), r(n + 1);
	for (int i = 1; i <= n; i++)
	{
		r[i] = 1;
		if (i < mr)
			r[i] = min(r[(mid << 1) - i], mr - i - 1);
		while (s[i + r[i]] == s[i - r[i]])
			++r[i];
		if (r[i] + i > mr)
		{
			mr = r[i] + i;
			mid = i;
		}
		if (s[i] != '/')
			vec.push_back(r[i] - 1);
	}
	return vec;
}

完事。

posted @ 2024-08-08 19:40  -wryyy-  阅读(6)  评论(0编辑  收藏  举报