• manacher 算法详细介绍和证明


    manacher 算法

    定义

    最大回文半径

    一个长度为奇数的回文串以中心为起点,到它某一端点的连线,所经过的字符个数,包括两端。
    例如:
    s = a b c d a s = abcda s=abcda

    c c c是回文串的中心,该字符串的最大回文半径是 3 3 3

    m r , L : mr ,L: mrL:

    mr 表示已经遍历的回文串的最靠右的端点,L表示最靠左的端点。
    初始化为0。

    m i d : mid : mid:

    表示当前 m r mr mr所对应的文串的中心的位置的下标。

    i : i : i:
    表示当前遍历到的字符的位置。

    i ’ : i’ : i:
    表示当前遍历到的字符的位置关于 m i d mid mid对称的点, i ′ = m i d ∗ 2 − i i' = mid * 2 - i i=mid2i,由于是刚刚遍历到 i i i那么 i i i一定在 m i d mid mid的右边, i ′ i' i一定在 m i d mid mid的左边。

    Manacher算法的作用

    对于每一个字符串里面的字符,我们都能以线性的时间复杂度求出其最长回文半径。

    Mancher算法的具体操作:

    1.初始化:

    首先,为了方便处理长度为偶数的字符串,我们把所有的偶字符串全部转化为长度为奇数的字符串。转化方式,对于原串的开头我们加上$结尾加上^,每个字符的两端我们加
    #

    例如,对于字符串:

    abcba

    abcba

    转化后的字符串

    $#a#b#c#b#a#^

    初始化代码:

    void init(char a[]){//把原始的串a转化为都是奇数长度的串b
    	int k = 0;
    	b[k ++] = '$',b[k ++ ] = '#';
    	for(int i = 0; i < n; i ++ ){
    		b[k ++ ] = a[i];
    		b[k ++ ] = '#';
    	}
    	b[k ++ ] = '^';
    	n = k;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    然后从左往右遍历字符串,设当前位置是 i i i,讨论并更新 m r , m i d , i mr, mid, i mr,mid,i

    1. i < m i d < m r , L < i ’ − p [ i ′ ] < m i d i < mid < mr, L < i’ - p[i'] < mid i<mid<mr,L<ip[i]<mid

    这时,根据对称性可知 p [ i ] = p [ i ′ ] p[i] = p[i'] p[i]=p[i]

    1. i < m i d < m r , i ′ − p [ i ′ ] < L i < mid < mr, i' - p[i'] < L i<mid<mr,ip[i]<L
      在这里插入图片描述
      这时
      p [ i ] = m r − i p[i] = mr - i p[i]=mri
      证明:假如 p [ i ] p[i] p[i]还能向右扩展,那么:
      在这里插入图片描述
      由对称性得:
      x x x y y y关于 i i i对称, 所以 x = y x = y x=y
      y y y a a a关于 m i d mid mid对称,所以 y = a y = a y=a,
      a a a b b b关于 i ′ i' i对称,所以 a = b a = b a=b
      所以 b = x b = x b=x。所以之前的 L , m r L,mr L,mr都可以向外继续扩展
      所以矛盾。
      3. i < m i d < m r , i ′ − p [ i ′ ] = = L i < mid < mr, i' - p [i'] == L i<mid<mr,ip[i]==L
      在这里插入图片描述
      这时,最小的 p [ i ] m i n = m r − i p[i]_{min} = mr - i p[i]min=mri

    但是不知道后面的情况,所以 p [ i ] ⩾ p [ i ] m i n p[i] \geqslant p[i]_{min} p[i]p[i]min
    我们需要暴力向 m r mr mr右边扩,知道找到 p [ i ] p[i] p[i]为止

    4. i > m r i > mr i>mr
    在这里插入图片描述

    这时先设 p [ i ] = 1 p[i] = 1 p[i]=1,再暴力向外扩张。

    代码

    void manacher(char b[]){
    	int mr = 0, mid;//mr不取到右端点,取的是开区间
    	for(int i = 1; i < n; i ++ ){
    		if(i < mr) p[i] = min(p[2 * mid - i], mr - i);
    		else p[i] = 1;
    		while(b[i - p[i]] == b[i + p[i]]) p[i] ++;
    		if(i + p[i] > mr){
    			mr = i + p[i];
    			mid = i;
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    时间复杂度
    O ( N ) O(N) O(N)
    因为 p [ i ] p[i] p[i]如果变大,超过 m r mr mr m r mr mr也是一直向右更新,所以里面的 w h i l e while while执行的次数为 n n n次,所以时间复杂度是 O ( N ) O(N) O(N)的。

    例题:
    manacher算法
    KFC Crazy Thursday

  • 相关阅读:
    VScode快捷键(win + mac)
    MFAN论文阅读笔记(待复现)
    R语言实践——rWCVP:世界维管植物名录的R包
    GO环境变量配置
    一个性能强到爆的RPC框架-gRPC
    VSCode的C/C++开发 ===> Windows
    测试左移和右移怎么做,这篇文章写的太详细了
    算法(二)——数组章节和链表章节
    并发bug之源(一)-可见性
    Jenkins-Android源码编译【架构设计】(适用鸿蒙/自动化/多产品/持续迭代)
  • 原文地址:https://blog.csdn.net/apple_52197802/article/details/126154024