求最大回文子串
M a n a c h e r Manacher Manacher算法。
在求解回文子串时,分奇偶处理比较麻烦,
m
a
n
a
c
h
e
r
manacher
manacher算法在每个字符两边加上一个
"
#
"
"\#"
"#"。
可以把偶数回文子串和奇数回文子串都扩充成奇数回文子串
# * # * #偶数情况,此时中心为#
# * # * # * #奇数情况,此时中心不为#
这里我们引入一个数组,表示的是:
p
[
i
]
p[i]
p[i]以
i
i
i为中心的回文半径
r
r
r,此时回文子串长度等于
2
r
+
1
2r+1
2r+1。#肯定比字母多一个,所以答案即为
r
r
r。遍历取最大值即可。
我们只需要求出
p
p
p数组,新增两个变量
m
x
mx
mx和
i
d
id
id,分别记录当前能通过回文串延伸到的最远位置,和这个回文串对应的中心位置。
考虑这样的一张图(盗我们集训队的,当前计算到了
i
i
i,之前
i
d
id
id位置的回文串更新到了此时最远的
m
x
mx
mx
如果
i
i
i比
m
x
mx
mx小,能够确认的回文半径一定是等同于
j
j
j的回文半径。因为回文串中是完全对称
的。
反之,如果
m
x
mx
mx更小,那么此时能确定的回文子串长度受最大的那个回文串长度的限制,一旦走出这部分,之前的性质就不成立了。
所以这一部分的确认是核心,但不能保证正确性,因为不能确认的部分也可能是回文串。所以我们还需要暴力判断,来增加长度。
不过值得注意的是:这部分核心做的操作就是,每次处理新结点的时候,更新到达的最远距离,最远距离每次都会被更新,最远距离以内的处理都是
O
(
1
)
O(1)
O(1)的,以外的处理会更新最远距离。从而整体是线性的。
O
(
l
e
n
)
O(len)
O(len)的优秀算法。
见代码:
string str;
int p[maxn];//回文半径,i+1...j
void manacher(string s){
memset(p,0,sizeof(p));
string tmp="";
for(int i=0;i<s.size();i++){
tmp+="#";
tmp+=s[i];
}
tmp+="#";
int mx=-1,id;//当前最远回文串所到的点和中心。
for(int i=1;i<tmp.size();i++){
p[i]=mx>i?min(p[2*id-i],mx-i):1;
while(i+p[i]<tmp.size()&&i+p[i]>=0&&tmp[i+p[i]]==tmp[i-p[i]])++p[i];//继续扩展。
p[i]--;
if(mx<i+p[i]){
mx=i+p[i];
id=i;
}
}
}
这里单独再介绍一些 m a n a c h e r manacher manacher可以处理的问题:
处理以i为结尾的回文串个数:
在处理回文串的时候,已知每个点作为中心能够到达的最长距离(
m
a
n
a
c
h
e
manache
manacher求出),相当
[
m
i
d
,
m
a
x
]
[mid,max]
[mid,max]的区域每个数的值都加
1
1
1。
第一 我们可以考虑用树状数组,显然是可以的,但未免增加码量。
第二 我们可以考虑用差分数组从而求出每个点的答案:
a
2
−
a
1
=
d
1
a2-a1=d1
a2−a1=d1
a
3
−
a
2
=
d
2
a3-a2=d2
a3−a2=d2
a
4
−
a
3
=
d
3
a4-a3=d3
a4−a3=d3
d
1
+
d
2
+
d
3
=
a
4
−
a
1.
d1+d2+d3=a4-a1.
d1+d2+d3=a4−a1.
所以可以推出:
a
n
=
∑
i
<
n
d
i
+
a
1
。
an=\sum_{i<n}{di}+a1。
an=∑i<ndi+a1。
这样就可以求出答案了。
我们在manacher种构建了新的回文串:
#
x
#
x
#
x
#
x
#
x
\#x\#x\#x\#x\#x
#x#x#x#x#x.
同时每个回文串最外面必定是
#
\#
#,字母位置除以
2
2
2就是原来的位置。
如果中心是字母,那么处理的时候max需要
−
1
-1
−1,因为每个#的位置除以
2
2
2等于的是接后字母的原位置,而我们需要的是前面一个。
如果中心是#,那么回文长度为
1
1
1的话就不需要考虑了。如果大于
1
1
1,最外面还是
#
\#
#,所以是相同的考虑方式。只是中心位置除以
2
2
2等于前面一个的位置,也是符合情况的。(
c
b
b
c
:
b
cbbc:b
cbbc:b和
b
b
bb
bb和
c
b
b
c
cbbc
cbbc都是回文串)。
处理的通式:
d
i
f
f
[
l
]
+
+
,
d
i
f
f
[
r
+
1
]
−
−
diff[l]++,diff[r+1]--
diff[l]++,diff[r+1]−−。这样在加和的时候,只有
[
l
,
r
]
[l,r]
[l,r]被加了
1
1
1.
for(int i=1;i<tmp.size();i++){
if(tmp[i]=='#'&&p[i]==1)continue;
diff[i/2]++,diff[(i+p[i]-1)/2+1]--;
}
pp[0]=diff[0];
for(int i=1;i<len;i++){
pp[i]=pp[i-1]+diff[i];
//cout<<pp[i]<<endl;
}