马拉车

Manachar算法主要是处理字符串中关于回文串的问题的,它可以在 O(n) 的时间处理出以字符串中每一个字符为中心的回文串半径
奇字符串和偶字符串找字串中心符的方式不同(偶是两个字符的中间,奇是中间的字符)查找时要分别讨论,比较麻烦。所以马拉车算法将所有字符串进行预处理,将每一个字符的前后插入一个字符串中不可能出现的字符,一般用#。于是新的字符串就是原字符串的二倍加一,也就是它一定是一个奇数。这样,对新字符串去讨论就方便很多
在这里插入图片描述
Manacher算法用一个辅助数组**Len[i]表示以字符T[i]为中心的最长回文字串的最右字符到T[i]**的长度,也就是半径加一
对于上图图,可以得出Len[i]数组为![在这里插入图片描述](https://img-blog.csdnimg.cn/20200810105528360.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xpeHV1dQ==,size_16,color_FFFFFF,t_70在这里插入图片描述

Len 数组有一个性质,那就是Len[i]-1就是该回文子串在原字符串S中的长度
证明:
首先在转换得到的字符串T中,所有的回文字串的长度都为奇数,那 么对于以T[i]为中心的最长回文字串,其长度就为2*Len[i]-1,经过观察可知,T中所有的回文子串,其中分隔符的数量一定比其他字符的数量多 1,也就是有Len[i]个分隔符,剩下Len[i]-1个字符来自原字符串,所以该回文串在原字符串中的长度就为Len[i]-1
在计算时:一开始还是要从左往右依次计算Len[i],而马拉车算法比暴力求的简单之处就是,已经求出来一些len[i]后,有一些len[i]可以通过一些规律去简单看出,或跳过一些计算步骤。
当计算Len[i]时,Lenj已经计算完毕。设P为之前计算中最长回文子串的右端点的最大值,并且设取得这个最大值的位置为po,分两种情况:
第一种情况:i<=P
那么找到i相对于po的对称位置,设为j,那么如果Len[j]<P-i,如下图:
在这里插入图片描述

那么说明以j为中心的回文串一定在以po为中心的回文串的内部,且j和i关于位置po对称,由回文串的定义可知,一个回文串反过来还是一个回文串,所以以i 为中心的回文串的长度至少和以j为中心的回文串一样,即Len[i]>=Len[j]。因为Len[j]<P-i,所以说以i为中心的最长字符串不会延申到p之外。由对称性可知Len[i]=Len[j]
如果Len[j]>=P-i,由对称性,说明以i为中心的回文串可能会延伸到P之外,而大于P的部分我们还没有进行匹配,所以要从P+1位置开始一个一个进行匹配,直到发生失配,从而更新P和对应的po以及Len[i]。
第二种情况:i>P
如果i比P还要大,说明对于中点为i的回文串还一点都没有匹配,这个时候,就只能老老实实地一个一个匹配了,匹配完成后要更新P的位置和对应的po以及Len[i]。

#include<stdio.h>
#include<string,h>
using namespace std;
const int maxn = 1e6 + 5;
char s[maxn * 2], str[maxn * 2];
int Len[maxn * 2], len;
void getstr() {//重定义字符串
 int k = 0;
 str[k++] = '@';//开头加个特殊字符防止越界
 for (int i = 0; i < len; i++) {
  str[k++] = '#';
  str[k++] = s[i];
 }
 str[k++] = '#';
 len = k;
 str[k] = 0;//字符串尾设置为0,防止越界
}
int manacher() {
 int mx = 0, id;//mx为最右边,id为中心点
 int maxx = 0;
 for (int i = 1; i < len; i++) {
  if (mx > i) Len[i] = min(mx - i, Len[2 * id - i]);//判断当前点超没超过mx
  else Len[i] = 1;//超过了就让他等于1,之后再进行查找
  while (str[i + Len[i]] == str[i - Len[i]]) Len[i]++;//判断当前点是不是最长回文子串,不断的向右扩展
  if (Len[i] + i > mx) {//更新mx
   mx = Len[i] + i;
   id = i;//更新中间点
   maxx = max(maxx, Len[i]);//最长回文字串长度
  }
 }
 return (maxx - 1);
}
int main() {
 scanf("%s", s);
 len = strlen(s);
 getstr();
 printf("%d\n",manacher());
 return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值