- 回文字符串分为奇回文和偶回文,在字符串中间插入任意字符使得串变成奇回文串
暴力思想:肯定是找一个点往两边任意扩展,遍历一次,
Manacher时间为O(n)
- 开一个数组
p[]
记录 以点i
为中点 的最长回文串的半径, - 假设前
i-1
个点的p[]
都已经求出来来,现在考虑p[i]
如何推导
重点: R
是p[1]~p[i-1]
中推出来的右端最远最长回文子串的右端点,且用pos
记录下来,每一步更新pos+R
,既然R
知道了,L
也知道(没啥意义)- 当前遍历到
i
点,j
是与i
对称的点,且 j = p o s − ( i − p o s ) j=pos-(i-pos) j=pos−(i−pos)->
j = 2 p o s − i j=2pos-i j=2pos−i,我们可以通过j
点来得到i
点的p[i]
值 - 此时
i j R
有三种情况
a. 第一个图中红色都在[L,R]
范围内,R-i>p[j]
,直接得到p[i]
的值,p[i]=p[j]
b.
此时j
点的回文子串只有一部分在[L,R]
(最大的回文子串)中,那么超出范围的肯定不符合要求,R-i<p[j]
所以p[i]=R-i
c. 剩下一个情况应该都看出来了,就是i>=R
,现在只能往两边暴力扩展了
总结步骤:
- 先更新
p[]
,有三种情况 - 向两边暴力扩展回文字符串,满足前两种情况这一步肯定扩展不出来新的回文串,所以不需要特殊判断是否
if(p[i]==1)
,所以这一步往两边扩展 - 更新
[L,R]
右端最远的回文子串,并且记录pos
的值, - 这是(插入字符的奇数字符串)上的一系列操作,我们要转化成源字符串上的位置与长度 这里是2个结论,
自己对比就可以知道了,
原字符串的位置:index = (resid - reslen)/2
源字符串的长度:len = reslen-1
- 在第一个添加一个与其他都不同的字符,就是为了
index
正确求出来
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int main() {
string t;
while ( cin>>t && t!="END" ) {
string s("%#");
for (int i=0; i<t.size(); i++) {
s += t[i];
s += "#";
}
vector<int> p(s.size(),0);
int id=0, mx=0, resid=0, reslen=0;
for (int i=1; i<s.size(); i++) {
p[i] = mx > i ? min(p[2*id-i],mx-i):1;
while ( s[i-p[i]] == s[i+p[i]] ) ++p[i];
if ( mx<i+p[i] ) {
mx = i+p[i];
id = i;
}
if ( reslen < p[i] ) {
reslen = p[i];
resid = i;
}
}
string ans=t.substr( (resid-reslen)/2 , reslen-1 );//求出来的最长回文子串
cout << ans.size() << endl;//长度
}
return 0;
}