Manacher算法是用来求最长回文子串的。
先回顾一下其他的高效做法。
后缀数组可以在nlogn的复杂度求出来,方法是将字符串reverse过后加在原字符串的末尾,中间用一个没有出现过的字符隔开,然后分奇偶两种情况依次枚举中点,问题就转换为LCP问题,进而转换为RMQ问题。
还有一种比较简洁的做法,分奇偶来二分长度,得到长度之后从两边跑,哈希出每一个长度固定的子串。nodgd证明过,当模的数在10^16左右时,出错率就在几亿分之一了。
Manchester算法针对这个问题可以达到O(N),是非常搞笑的。思想有点类似扩展KMP,这里用了一个P数组,P[i]表示i为中点的最长回文串的半径,然后发现覆盖到最长的一个点有很高的利用价值。如果覆盖到当前需要求的点,可以找关于那个最长点的对称点作为这个点的P值;如果没有覆盖到当前的点,就暴力来扩展,并更新最长点。由于每个点只被扩展一次,所以是O(N)的。但是这里只考虑了为奇数长度的回文串,有个巧妙的地方就是可以把相邻两个字符之间插入一个统一的未出现过的字符,这样就一定是奇数长度了。
由于P这个变量名太常用了,实现的时候可以换个名字,或者封装起来。
const int MAXN = 1000010;
char s[MAXN];
int len;
namespace Manacher
{
char s2[MAXN * 2];
int p[MAXN * 2], n2, mxi, ans;
void prepare()
{
n2 = 0;
s2[n2++] = '{';
s2[n2++] = '#';
for (int i = 0; i<len; ++i)
s2[n2++] = s[i], s2[n2++] = '#';
s2[n2++] = '}';
}
int solve()
{
prepare();
ans = 0, mxi = 0;
p[0] = 1;
for (int i = 1; i<n2; ++i)
{
if (p[mxi]+mxi > i)
p[i] = min(p[mxi*2-i], mxi+p[mxi]-i);
else p[i] = 1;
for (; s2[i+p[i]]==s2[i-p[i]]; ++p[i])
if (p[i]+i > p[mxi]+mxi) mxi = i;
}
for (int i = 0; i<n2; ++i)
ans = max(ans, p[i]);
return ans - 1;
}
};
ural1297,裸的最长回文子串,并且数据范围可以暴力,但是要求输出子串。这个好办,先求max,再扫描一遍输出第一个等于max的。
hdu3294,也基本上是裸的,但是要做一点字符映射。我一开始把样例的意思理解错了,结果主函数重新写了一遍,浪费了很多时间。这次主要是因为英文题目不想认真看,以后至少还是要把样例理解了再写。