Manacher Algorithm

Manacher Algorithm

Gleen K. Manacher (1975), "A new linear-time 'on-line' algorithm for finding the smallest initial palindrome of a string"

一种关于一个字符串 \(s\) 的回文子串的算法, 可以线性求两个数组 \({f_1}_i\)\({f_2}_i\), 分别表示以第 \(i\) 位为中心的最长奇回文子串和最长偶回文子串的半径 (长度除以 \(2\), 向上取整). 对于以 \(i\) 为中心的回文子串 \([l, r]\), 规定 \(i = \lceil \frac{l + r}{2} \rceil\).

根据回文串的性质可以知道 \({f_1}_i\)\({f_2}_i\) 也恰好是以 \(i\) 为中心的回文子串的个数. 这条性质非常重要.

朴素

在求字符 \(i\)\(f_1\) 时, 每次从 \({f_1}_i = 1\) 开始枚举, 只要 \(s_{i + {f_1}_i} = s_{i - {f_1}_i}\), 就让 \({f_1}_i\) 增加 \(1\).

对于 \(f_2\) 同样如此, 只是边界换成 \(f_2 = 0\), 自增条件变成 \(s_{i + {f_2}_i} = s_{i - {f_2}_i - 1}\).

容易看出, 这样最坏的复杂度可达 \(O(n^2)\), 在 \(s\) 形如 aaaa...aaaa 时可以出现.

优化

还是先考虑 \(f_1\) 的求法.

由于朴素算法外层循环是从小到大按顺序的, 所以可以认为求 \({f_1}_i\) 时, \({f_1}_j~(j \in [1, i))\) 已经求出来了.

设当前 \(j + {f_1}_j - 1\) 最大的 (对应回文子串右边界最大) 的 \(j\)\(Frontier\), 则对 \(i\) 有两种情况:

\(i > Frontier + {f_1}_{Frontier} - 1\)

这时的 \(i\) 不在任何已知的回文子串中, 则直接朴素求 \({f_1}_i\).

\(i \leq Frontier + {f_1}_{Frontier} - 1\)

这时的 \(i\) 在以 \(Frontier\) 为中心的回文串内, 则必有 \(s_{i} = s_{2Frontier - i}\). 内涵是因为 \(i\)\(Frontier\) 的右边, 又在以 \(Frontier\) 为中心的奇回文串内, 则在 \(Frontier\) 左边的对应位置也应该有一个同样的字符. 设这个字符位置为 \(Reverse = 2Frotier - i\).

因为 \(Reverse\)\(Frontier\) 左边, 所以 \({f_1}_{Reverse}\) 也一定求出来了, 所以 \([Reverse - {f_1}_{Reverse} + 1, Reverse + {f_1}_{Reverse} - 1]\) 是回文串, 再加上 \([Frontier - {f_1}_{Frontier} + 1, Frontier + {f_1}_{Frontier} - 1]\) 也是回文串, 所以 \([Reverse - {f_1}_{Reverse} + 1, Reverse + {f_1}_{Reverse} - 1]\)\([Frontier - {f_1}_{Frontier} + 1, 2Reverse - Frontier + {f_1}_{Frontier} - 1]\) 皆为回文串. 二者的中心都是 \(Reverse\), 设二者的交集为 \([PalindromeL, PalindromeR]\), 则 \([2Frontier - PalindromeR, 2Frontier - PalindromeL]\) 也是回文串.

这时 \([2Frontier - PalindromeR, 2Frontier - PalindromeL]\) 的中心是 \(2Frontier - \frac{PalindromeL + PalindromeR}{2} = 2Frontier - Reverse = i\).

这时, 可以断定 \({f_1}_i \geq Reverse - PalindromeL + 1\).

接下来, 考虑 \({f_1}_i\) 是否能取更大值.

  • \(Reverse - {f_1}_{Reverse} + 1 < Frontier - {f_1}_{Frontier} + 1\)

    这时如果 \({f_1}_i > Reverse - PalindromeL + 1\), 则 \(s_{Frontier + {f_1}_{Frontier}} = s_{2i - Frontier - {f_1}_{Frontier}} = s_{3Frontier - 2i + {f_1}_{Frontier}}\). 字符 \(3Frontier - 2i + {f_1}_{Frontier}\) 关于 \(Reverse\) 的对称字符就是 \(Frontier - {f_1}_{Frontier}\), 因为 \(Reverse - {f_1}_{Reverse} + 1 < Frontier - {f_1}_{Frontier} + 1\), 所以字符 \(Frontier - {f_1}_{Frontier}\) 也和前面提到的三个字符相同. 这样, \(s_{Frontier - {f_1}_{Frontier}} = s_{Frontier + {f_1}_{Frontier}}\). 但是 \({f_1}_{Frontier}\) 则说明 \(s_{Frontier - {f_1}_{Frontier}} \neq s_{Frontier + {f_1}_{Frontier}}\), 相矛盾, 所以 \({f_1}_{Frontier}\) 不能更大.

  • \(Reverse - {f_1}_{Reverse} + 1 > Frontier - {f_1}_{Frontier} + 1\)

    根据已经求出的 \(f_{Reverse}\) 得, \(s_{Reverse - {f_1}_{Reverse}} \neq s_{Reverse + {f_1}_{Reverse}}\). 又因为 \(Reverse - {f_1}_{Reverse} \geq Frontier - {f_1}_{Frontier} + 1\), 所以对称的 \(s_{2Frontier - Reverse + {f_1}_{Reverse}} \neq s_{2Frontier - Reverse - {f_1}_{Reverse}}\), 所以这种情况 \({f_1}_i\) 也不会更大.

  • \(Reverse - {f_1}_{Reverse} + 1 = Frontier - {f_1}_{Frontier} + 1\)

    这时比较特殊, 因为 \(s_{Reverse - {f_1}_{Reverse}} \neq s_{Reverse + {f_1}_{Reverse}}\), \(s_{Frontier - {f_1}_{Frontier}} \neq s_{Frontier + {f_1}_{Frontier}}\). 所以就算是 \(s_{Frontier + {f_1}_{Frontier}} = s_{2i - Frontier - {f_1}_{Frontier}} = s_{Reverse + {f_1}_Reverse}\) 也无所谓, 这不会和任何已经求出的 \(f_1\) 值冲突. 所以在 \({f_1}_i = Reverse - PalindromeL + 1\) 的基础上继续朴素即可.

对于求 \(f_2\) 的情况, 思路相同, 实现有细节上的差别, 不再赘述.

时间复杂度

\(f_1\) 为例.

在大部分情况下, 每个 \(f_1\) 的求出是 \(O(1)\) 的, 只要考虑朴素时的复杂度即可.

  • \(Frontier + {f_1}_{Frontier} - 1 < i\)

    一定有 \(Frontier + {f_1}_{Frontier} - 1 < i + {f_1}_i - 1\), \(Frontier\) 被更新成 \(i\), \(Frontier + {f_1}_{Frontier}\) 比原先多了至少 \({f_1}_i\), 即本次操作步数.

  • \(Frontier + {f_1}_ {Frontier} - 1 \leq i\), 但 \(Frontier + {f_1}_{Frontier} - 1 = Reverse + {f_1}_{Reverse} - 1\) 时.

    朴素从原来的 \({f_1}_i\) 开始, 这时 \(i + {f_1}_i - 1= Frontier + {f_1}_{Frontier} - 1\), 接下来每执行一步, \(i + {f_1}_{Frontier}\) 都增加 \(1\).

因为每次朴素执行 \(k\) 步后, \(Frontier + {f_1}_{Frontier}\) 都右移至少 \(k\) 位, 而 \(Frontier + {f_1}_{Frontier}\) 最大是 \(n\), 所以朴素的总复杂度是 \(O(n)\).

综上, Manacher 可以在 \(O(n)\), 求出长度为 \(n\) 的字符串的所有回文子串.

模板

一个长度为 \(n\) 的字符串, 求最长回文子串长度. (\(n \leq 1.1*10^7\))

\(Manacher\) 求出 \(f_1\), \(f_2\), 第 \(i\) 个字符为中心的最长回文子串即为 \(\max(2{f_1}_i - 1, 2{f_2}_i)\), 求 \(f_1\), \(f_2\) 时顺便统计即可.

代码, 缺省源省略

unsigned n, Frontier(0), Ans(0), Tmp(0), f1[11000005], f2[11000005];
char a[11000005];
int main() {
	fread(a+1,1,11000000,stdin);        // fread 优化 
  n = strlen(a + 1);                  // 字符串长度 
  a[0] = 'A';
  a[n + 1] = 'B';                     // 哨兵 
  for (register unsigned i(1); i <= n; ++i) {   // 先求 f1 
    if(i + 1 > Frontier + f1[Frontier]) {       // 朴素 
      while (!(a[i - f1[i]] ^ a[i + f1[i]])) {
        ++f1[i];
      }
      Frontier = i;                             // 更新 Frontier 
    }
    else {
      register unsigned Reverse((Frontier << 1) - i), A(Reverse - f1[Reverse]), B(Frontier - f1[Frontier]);
      f1[i] = Reverse - ((A < B) ? B : A);                      // 确定 f1[i] 下界 
      if (!(Reverse - f1[Reverse] ^ Frontier - f1[Frontier])) { // 特殊情况 
        while (!(a[i - f1[i]] ^ a[i + f1[i]])) {
          ++f1[i];
        }
        Frontier = i;                                           // 更新 Frontier 
      }
    }
    Ans = ((Ans < f1[i]) ? f1[i] : Ans);
  }
  Ans = (Ans << 1) - 1;                             // 根据 max(f1) 求长度 
  Frontier = 0;
  for (register unsigned i(1); i <= n; ++i) {
    if(i + 1 > Frontier + f2[Frontier]) {           // 朴素 
      while (!(a[i - f2[i] - 1] ^ a[i + f2[i]])) {
        ++f2[i];
      }
      Frontier = i;                                 // 更新 Frontier 
    }
    else {
      register unsigned Reverse ((Frontier << 1) - i - 1), A(Reverse - f2[Reverse]), B(Frontier - f2[Frontier]);
        f2[i] = Reverse - ((A < B) ? B : A);        // 确定 f2[i] 下界 
      if (A == B) { // 特殊情况, 朴素 
        while (a[i - f2[i] - 1] == a[i + f2[i]]) {
          ++f2[i];
        }
        Frontier = i;                               // 更新 Frontier 
      }
    }
    Tmp = ((Tmp < f2[i]) ? f2[i] : Tmp);
  }
  Tmp <<= 1;                                        // 根据 max(f2) 求长度 
  printf("%u\n", (Ans < Tmp) ? Tmp : Ans);          // 奇偶取其大 
  return Wild_Donkey;
}
posted @ 2021-04-13 18:30  Wild_Donkey  阅读(65)  评论(0编辑  收藏  举报