Loading

【算法】manacher

1. 算法简介

Manacher 算法,俗称马拉车。是一个可以在线性时间复杂度内高效解决最大回文子串的问题。

2. 算法流程

暴力想必大家也都会,就是枚举中心点然后暴力扩展长度。时间复杂度 \(O(n^2)\)

还有就是字符串哈希 + 二分:枚举中心点,将暴力的扩展变成二分。因为长度越长更不能回文,长度越短更能回文,满足单调性。判断子串相同就用哈希即可。这样可以做到时间复杂度 \(O(n\log n)\)

最后是 manacher 算法,就是利用那些已经计算过的信息看来推导没有计算过的信息。

首先处理一下奇回文和偶回文的情况:在字符串的开头、结尾以及每一个字符之间插入一个没有在串中出现过的字符 &$#,可以发现这样处理过后的字符串的最大回文串的开头结尾均为 "插入字符",原最大回文串长为新串的最大回文半径长度 \(-1\)

对于每一个位置 \(i\) 维护一个最长回文半径 \(d_i\),并维护一个全局最长回文半径 \(R\) 以及她所在的中心位置 \(M\),然后分类讨论。

  • \(i>r\),显然无法利用原有的信息,直接暴力去做。
  • \(i\le r\),找到 \(i\) 在全局最长回文串下对应的 \(k=2M-i\) 位置,可以分为两类;
    • 如果 \(d_k\) 对应的区间被包含于 \([2M-R,R]\) 区间中,则 \(d_k=d_i\)
    • 如果 \(d_k\) 对应的区间没有被包含于 \([2M-R,R]\) 区间中,则令 \(d_k=R-i+1\) 然后暴力匹配;

根据下图加深理解:

  1. 如果 \(d_k\) 对应的区间被包含于 \([2M-R,R]\) 区间中,则 \(d_k=d_i\)

image

  1. 如果 \(d_k\) 对应的区间没有被包含于 \([2M-R,R]\) 区间中,则令 \(d_k=R-i+1\) 然后暴力匹配;

image

3. 算法实现

#include<bits/stdc++.h>
#define int long long
#define reg register 
#define For(i,l,r) for(reg int i=l;i<=r;++i)
#define FOR(i,r,l) for(reg int i=r;i>=l;--i)

using namespace std;

const int N = 5e7; 

int n, d[N];

char s[N];

string t = " ";

int Manacher() {
  int ans = 0;
  for (reg int i = 1, M = 0, r = 0; i <= n; ++i) {
    if(i <= r) d[i] = min(d[2 * M - i], r - i + 1);
    while(i - d[i] >= 1 && i + d[i] <= n && t[i - d[i]] == t[i + d[i]]) d[i]++;
    if(i + d[i] - 1 > r) M = i, r = i + d[i] - 1;
    ans = max(ans, d[i] - 1);
  }
  return ans;
}

signed main() {
  ios::sync_with_stdio(0);
  cin.tie(0), cout.tie(0);
  cin >> (s + 1);
  n = strlen(s + 1);
  t += '#';
  For(i,1,n) {
    t += s[i], t += "#";
  }
  n = 2 * n + 1;
  cout << Manacher() << '\n';
  return 0;
}

后记

可以拿 Z 函数和 Manacher 的关键代码进行对比:

Manacher:

for (reg int i = 1, M = 0, r = 0; i <= n; ++i) {
  if(i <= r) d[i] = min(d[2 * M - i], r - i + 1);
  while(i - d[i] >= 1 && i + d[i] <= n && t[i - d[i]] == t[i + d[i]]) d[i]++;
  if(i + d[i] - 1 > r) M = i, r = i + d[i] - 1;
}

Z 函数:

for (reg int i = 2, l, r = 0; i <= m; ++i) {
  if(i <= r) z[i] = min(z[i - l + 1], r - i + 1);
  while(i + z[i] <= m && t[1 + z[i]] == t[i + z[i]]) z[i]++;
  if(i + z[i] - 1 > r) l = i, r = i + z[i] - 1;
}

这俩玩意的代码简直一模一样,相似度 \(85\%\),所以她们俩也很好背。

posted @ 2024-11-19 17:38  Daniel_yzy  阅读(5)  评论(0编辑  收藏  举报