Manacher:求最大回文子串

求最大回文子串

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 a2a1=d1
a 3 − a 2 = d 2 a3-a2=d2 a3a2=d2
a 4 − a 3 = d 3 a4-a3=d3 a4a3=d3

d 1 + d 2 + d 3 = a 4 − a 1. d1+d2+d3=a4-a1. d1+d2+d3=a4a1.
所以可以推出: a n = ∑ i &lt; n d i + a 1 。 an=\sum_{i&lt;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;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值