Manacher(马拉车)

Manachar算法主要是处理字符串中关于回文串的问题的,它可以在 O(n) 的时间处理出以字符串中每一个字符为中心的回文串半径.因为它只对没有匹配过的位置进行匹配。

我们在字符串中的每一个字符前后 都加一个特殊符号,例如 “#”,然后在字符串最后再加一个。那么就让字符串变成奇数了。因为如果字符串长度为n的话, “#” 的数量为n+1
2*n+1 为奇数。

  • 例如原字符串为 “ababa” 长度为5,奇数。

  • 处理后的字符串为“ #a#b#a#b#a# ” 长度变为11,奇数。

  • 原字符串为“abba” ,长度为4,偶数。

  • 处理后的字符串为 “#a#b#b#a#” 长度为9,奇数。

  • 这样就不用考虑奇偶问题了。因为处理后的字符串恒为奇数。
    在这里插入图片描述
    Len数组有一个性质,那就是Len[i]-1就是该回文子串在原字符串S中的长度,所有的回文字串的长度都为奇数,那么对于以T[i]为中心的最长回文字串,其长度就为2*Len[i]-1,T中所有的回文子串,其中分隔符的数量一定比其他字符的数量多1,也就是有Len[i]个分隔符,剩下Len[i]-1个字符来自原字符串,所以该回文串在原字符串中的长度就为Len[i]-1。

首先对数据进行初始化,再对i进行判断,分为两种情况
第一种情况,i>=mx,i在mx前面,直接让len[i]=1。
第二种情况,i<mx,这时候就又有两种情况了,对len[j]和mx-i进行比较:

(1)len[j]<=mx-i说明i的最右端还在mx里面,如上图所示,只需要让len[i]=len[j]即可。

(2)len[j]>mx说明i的最右端大于mx了,所以我们需要对这两种情况再讨论一下,当Len[j] < mx-i的时候,表示Len[i]的长度可能不会超过mx-i,所以我们就从i的Len[2*id - i]也就是Len[mx-i]的地方开始匹配。当Len[j] > mx - i的时候,说明i位置的子串长度超过了mx,但mx以外的地方还没有遍历到,所以我们就暂且将len[i]=mx-i赋值一个较小的且能够遍历到的,然后再以i为中心,以mx-i为半径向两边更新,如果len[i]+i>mx,就更新len[i]+i为最右边,中点id更新为i,再比较更新最长回文子串的长度。
在这里插入图片描述

#include <iostream>
#include <algorithm>
using namespace std;
const int N=3*1e7+5000;
char s[N],str[N];
int p[N];

void init()
{
    int k=0;
    str[k++]='@';//开头加个特殊字符防止从中间向两边扩散的时候越界
    str[k++]='#'; //在每个字符前加个符号来统一奇偶
    for(int i=0;s[i];i++)
    {
        str[k++]=s[i];
        str[k++]='#';   //在每个字符后也加入一个符号来统一奇偶
    }

}
int main()
{

    cin>>s;
    init();
    int id=0,mx=0,maxx=0;   //mx就是当前遍历过的最右边的位置,id表示回文子串的中心
    for(int i=1;str[i];i++)   //跳过第一个特殊字符
    {
        if(i<mx)//如果i小于mx表示还没超过mx,所以进行判断以i为中心的半径会不会大于mx
         {
          if(p[2*id-i]<mx-i) //根据之前求过的i的对称点j判断i的半径不会大于mx-i
                 p[i]=p[2*id-i]; //比较取一个短的
             else p[i]=mx-i;  
         }
        else p[i]=1;    //大于了就只能保证本身是回文
        while(str[i+p[i]]==str[i-p[i]])   //向两边进行扩散
            p[i]++;
        if(i+p[i]>mx)  //如果超出当前的最右边就更新
        {

            id=i;
            mx=i+p[i];
            maxx=max(maxx,p[i]);
        }

    }
    cout<<maxx-1;

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值