Manacher算法(马拉车??)

(本文最后判断区间回文有改动,emm之前写错了,你这么聪明一定看出来了)

该算法用于寻找当前串中最长的回文子串长度。

为了避免误人子弟,也可以直接去看思路清晰,简单易懂的博客:https://blog.csdn.net/dyx404514/article/details/42061017

这个大神写的各种算法的模板和讲解都很棒哦。

①暴力找:

我们选取一个点O,然后要考虑这个点作为回文串中心可能形成最长回文串的三种情况:

回文长度为奇数的情况

xxOxx

回文长度为偶数的情况:

xxxOxx

xxOxxx

O(N²)的复杂度妥妥的。


②Manacher:

首先该算法是O(N)的。

然后解决了上述需要分三种情况讨论的问题。

假设一个待匹配的串为aabaa

我们会把他变成

¥#a#a#b#a#a#$

也就是用(字符串长度+1个)#把字符一个个分开,旁边再加两个不同的字符防止匹配的时候越界。

为什么这样处理就可以不用分情况了呢?

这个问题我们在构造好了我们的Len数组,了解这个数组的含义了之后再说。

我们对这个船新的串使用manacher算法


③Len数组含义与构造:

Len[i]表示以i为中心的回文串往右边延伸了多少

eg:¥#a#a#b#a#a#$,Len【6】 =  6,Len【2】= 2 ,Len【1】 = 1

构造核心思想与kmp,exkmp差不多,就是根据已经匹配的,让当前能够最大限度的偷懒(得到当前已知匹配的部分)

PO表示当前Len能够向右延伸最远的回文串中点坐标。

mx就是延伸最右位置的坐标。

这两个都是辅助用于判断当前i位置有几位已经确定回文可以不用比了。

具体匹配过程的图示:(红色块最右即为mx)

板子:(主函数 字符串从0开始输入)

const int maxn=1000010;
char str[maxn];//原字符串
char tmp[maxn<<1];//转换后的字符串
int Len[maxn<<1];
//转换原始串
int INIT(char *st)
{
    int i,len=strlen(st);
    tmp[0]='@';///开头加一个不等于#也不等于字符串的字符,这样就不用判断左边越界了,那么右边万一比到n+1怎么办呢?有\0吗?不,\0在n处,解决办法看16行
    for(i=1;i<=2*len;i+=2)
    {
        tmp[i]='#';
        tmp[i+1]=st[i/2];
    }
    tmp[2*len+1]='#';
    tmp[2*len+2]='$';///结尾搞一个不是@也不是#的字符
    tmp[2*len+3]=0;
    return 2*len+1;//返回转换字符串的长度
}
//Manacher算法计算过程
int MANACHER(char *st,int len)
{
     int mx=0,ans=0,po=0;//mx即为当前计算回文串最右边字符的最大值
     for(int i=1;i<=len;i++)
     {
         if(mx>i)
         Len[i]=min(mx-i,Len[2*po-i]);//在Len[j]和mx-i中取个小
         else
         Len[i]=1;//如果i>=mx,要从头开始匹配
         while(st[i-Len[i]]==st[i+Len[i]])
         Len[i]++;
         if(Len[i]+i>mx)//若新计算的回文串右端点位置大于mx,要更新po和mx的值
         {
             mx=Len[i]+i;
             po=i;
         }
         ans=max(ans,Len[i]);
     }
     return ans-1;//返回Len[i]中的最大值-1即为原串的最长回文子串额长度
}

实在理解不了去看看exkmp,真的感觉是一毛一样的:https://blog.csdn.net/weixin_43768644/article/details/94644776


④:Len数组的使用

注意Len数组使用是都是看原字符串第几个字符(1~n),不是看下标(0~i-1),这个比较重要

(0)基本的认识

         Len[i]对应两种情况:

         ①当前是‘#’时,Len[i] 必定为奇数,eg:a#a  #  a#b,Len[中间'#'的下标] = 3

         那么这个Len[i]就对应原字符串中偶数长度的回文串,你可以求原串的回文中点(i/2表示靠左的中点),然后最大回文半径

         等于Len[i]/2     (因为奇数会向下取整)

         ②当前是字母,Len[i]比为偶数,eg:a#a# b #a#b 对应奇数长度回文串,i/2表回文中点,Len[中间'b'的下标] = 4

         i/2是回文中心,Len[i]/2是回文半径(=2,对于aba)

         下面这三个应该没什么用。

         i表示第i个字符

         判断当前点作为奇数长度回文中点的最长回文长度  --->Len[i*2]-1

         判断当前点作为偶数长度回文左中点的最长回文长度  --->Len[i*2+1]-1

         判断当前点作为偶数长度回文左中点的最长回文长度  --->Len[i*2-1]-1

(1)判断原字符串中[l,r]是否回文(l,r表示第几个字母)

         Len[l+r]-1>=r-l+1    ?  是回文:不是回文

         证明方法:根据给出区间长度奇偶分类,运用(0)里面的基本知识判断就可以

(2)求出整个串的回文串总数

         根据(0)所说的,遍历加了‘#’的那个串分析每个点做中点的最长回文半径即可

(3)求出本质不同的回文串的数量

         分析:首先每一个点开始匹配前会先尽可能得到已知匹配部分来减少当前匹配,然后这部分我们不用判断是否出现过,因为肯定出现过。然后剩余要匹配部分,每次都对新产生这个回文串判断是否出现过,(可能出现也可能没出现过),马拉车+哈希表去重即可实现。(模板题:hihocoder1602)

做这些事情的复杂度和耗费的空间都比回文自动机小(大概),然后处理回文问题两者要灵活结合使用。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值