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;
}
完事。