Manacher (马拉车算法)

算法功能

回文字符串的通俗定义是:如果一个字符串正着读或反着读都一样,那么称这个字符串为回文字符串。

Manacher的作用就是在==O(N)==的时间复杂度下求出以每个位置为回文中心的最长回文半径。

前置知识

回文中心

此时发现如果回文字符串长度为偶数时,回文中心不能恰好落到某个数组下标处,为了统一操作,在每个字符中间添加一个特殊字符,如:

最长回文半径

以iii下标为回文中心的回文半径,可以理解为人的臂长。例如 上图红色的4

注意:有的资料中是把回文中心本身也计入回文半径的长度(在上图基础上每个回文半径+1),相比之下我更喜欢不计入的方式。因为这样无论字符串长度是奇数还是偶数,其最长回文长度,就是max(r[i])max(r[i])max(r[i])

核心思想

暴力中心扩展

如果用暴力匹配&中心扩展的方式,遍历到每一个字符时都以该字符作为回文中心向两侧进行扩展,扩展的最大步数,即为以该点为回文中心的回文半径,时间复杂度O(n2)O(n^2)O(n2)

#include <iostream>
#include <cstdio>
#include <cstring>using namespace std;int r[105];//将字符之间填充#字符,方便统一操作
string GetNewStr(string &str) {string s = "#";for (int i = 0; str[i]; ++i) {(s += str[i]) += '#';}return s;
}int main() {string str;cin >> str;str = GetNewStr(str);for (int i = 0; str[i]; ++i) {while (i - r[i] >= 0 && str[i - r[i]] == str[i + r[i]])  ++r[i];--r[i];    //将自身减掉}for (int i = 0; str[i]; ++i) {printf("%3c", str[i]);}printf("\n");for (int i = 0; str[i]; ++i) {printf("%3d", r[i]);}printf("\n");return 0;
}/*aba#  a  #  b  #  a  #0  1  0  3  0  1  0acca#  a  #  c  #  c  #  a  #0  1  0  1  4  1  0  1  0*/
优化

Manacher的核心思想就是尽可能借助之前已知的回文串,来减少以当前点为回文中心向两侧扩展的操作次数

我们发现,暴力方法在扫描完str[4]str[4]str[4]后其实已经知道了str[0−8]str[0-8]str[0−8]为回文串了。那么在扫描str[5]时,可以利用与当前已知可以向右扩展最远的回文中心(4)的对称点3的回文长度来减少扫描str[5]的操作次数

扫描str[i] 即是以str[i]作为回文中心向两侧探索

说人话就是:

  • 扫描完ccc下标字符后,已经发现了xxx~yyy是回文串,并且在这之前没有扫描过哪个回文串的右边界>yyy。也就是说当前以ccc下标作为回文中心的回文串,是向右扩展最远的

  • 此时又探索到了iii下标位置,发现iii下标,还在当前已知最远回文串中。这时就可以利用iii基于ccc的对称点i’i’i’的值来减少iii点向两侧扩展的次数

​ (1)第一种情况,r[i′]r[i']r[i′]并没有超出xxx~yyy,所以r[i]=r[i′]r[i] = r[i']r[i]=r[i′]

​ (2)第二种情况,r[i′]r[i']r[i′]并超出x~y,所以r[i]=c+r[c]−ir[i] = c + r[c] - ir[i]=c+r[c]−i

  • 再通过暴力向两侧扩展r[i]r[i]r[i]
  • 经过优化之后,就可以借助之前的回文串,从而减少当前点的扫描扩展次数。注意过程中记录更新ccc的位置

P3805 模板manacher 算法

#include <iostream>
#include <cstdio>
#include <cstring>using namespace std;string Get_new(string &str) {string temp = "#";for (int i = 0; str[i]; ++i) {(temp += str[i]) += "#";}return temp;
}int main() {string str;cin >> str;str = Get_new(str);int *r = (int *)calloc(sizeof(int), str.size()), c = 0, ans = 0;for (int i = 1; str[i]; ++i) {if (c + r[c] > i)  r[i] = min(r[2 * c - i], c + r[c] - i);while (i - r[i] >= 0 && str[i - r[i]] == str[i + r[i]])  ++r[i];--r[i];if (i + r[i] > c + r[c])  c = i;   //注意更新C的位置ans = max(ans, r[i]);}cout << ans << endl;free(r);return 0;
}

时间复杂度证明

我们考虑产生复杂度的地方,分别是最外层对整个字符串的遍历和每次对回文串扩展的while。

显然,最外层的for循环是O(N)的,那么我们只需要证明while的总循环次数也是O(N)级别的即可。

1、如果以i位置的为回文中心的回文串的基础长度没有超出当前回文串的最大覆盖范围,那么显然不会进入while中(由于对称性,该回文串的长度已经最大了)。

2、如果以i位置的为回文中心的回文串的基础长度超出了当前回文串的最大覆盖范围,那么显然每进入一次while,回文串的最大覆盖范围都会加1.那么,当最大覆盖范围包含了整个字符串之后,while循环就不会在进入了。所以while循环的总次数为O(N)。综上所述,Manacher算法的时间复杂度为O(N)。

例题:

P1659 [国家集训队]拉拉队排练

#include <iostream>
#include <cstdio>
#include <cstring>using namespace std;const int Mod = 19930726;const int N = 1e6;int r[N + 5], cnt[N + 5], c = 0;int quick_pow(long long n, long long p) {int ans = 1;while (p) {if (p & 1)  ans = (ans * n) % Mod;n = (n * n) % Mod;p >>= 1;}return ans % Mod;
}int main() {int n, max_len = 0;long long m;string str;cin >> n >> m >> str;for (int i = 0; str[i]; ++i) {if (c + r[c] > i)  r[i] = min(r[2 * c - i], c + r[c] - i);while (i - r[i] >= 0 && str[i - r[i]] == str[i + r[i]])  ++r[i];--r[i];++cnt[1];--cnt[r[i] * 2 + 2];max_len = max(max_len, r[i] * 2 + 1);if (i + r[i] > c + r[c])  c = i;}for (int i = 1; i <= max_len; ++i) {cnt[i] += cnt[i - 1];}long long ans = 1;for (int i = max_len; i > 0 && m; i -= 2) {if (m >= cnt[i]) {ans = (ans * quick_pow(i, cnt[i])) % Mod;m -= cnt[i];} else {ans = (ans * quick_pow(i, m)) % Mod;m = 0;}}if (m)  cout << -1 << endl;else cout << ans << endl;return 0;
}

P4555 [国家集训队]最长双回文串

#include <iostream>
#include <cstdio>
#include <cstring>using namespace std;const int N = 2e5;int r[N + 5], Left[N + 5], Right[N + 5], c;string Get_new(string &str) {string temp = "#";for (int i = 0; str[i]; ++i) {(temp += str[i]) += "#";}return temp;
}int main() {string str;cin >> str;str = Get_new(str);for (int i = 0; str[i]; ++i) {if (c + r[c] > i)  r[i] = min(r[2 * c - i], c + r[c] - i);while (i - r[i] >= 0 && str[i - r[i]] == str[i + r[i]])  ++r[i];--r[i];if (c + r[c] < i + r[i])  c = i;Left[i + r[i]] = max(Left[i + r[i]], max(r[i], 1));Right[i - r[i]] = max(Right[i - r[i]], max(r[i], 1));}for (int i = 2; i < (int)str.size(); i += 2) {Right[i] = max(Right[i], Right[i - 2] - 2);}for (int i = str.size() - 3; i > 0; i -= 2) {Left[i] = max(Left[i], Left[i + 2] - 2);}/*for (int i = 0; str[i]; ++i) {printf("%3c", str[i]);}printf("\n");for (int i = 0; str[i]; ++i) {printf("%3d", r[i]);}printf("\n");for (int i = 0; str[i]; ++i) {printf("%3d", Left[i]);}printf("\n");for (int i = 0; str[i]; ++i) {printf("%3d", Right[i]);}printf("\n");*/int ans = 0;for (int i = 2; i < (int)str.size() - 1; i += 2) {ans = max(ans, Left[i] + Right[i]);}cout << ans << endl;return 0;
}

Manacher (马拉车算法)相关推荐

  1. 【算法】Manacher(马拉车)算法

    原jekyll 2019-09-07 Manacher's Alogrithm,中文名叫马拉车算法,是一位叫Manacher的人在1975年提出的一种算法,解决的问题是求最长回文子串,算法的神奇之处就 ...

  2. Manacher马拉车算法求最长回文子串

    终于把马拉车算法搞明白了!赶紧记录一下. 这个算法用于查找一个字符串的最长回文子串 马拉车算法依次给数组p[i]赋值,马拉车算法的本质就是在每次给数组p[i] 赋值时尝试进行偷懒 例如,当要给p[6] ...

  3. Manacher(马拉车)算法—简略讲解

    这是一篇菜鸡的算法小笔记!希望你喜欢! 前言 马拉车算法是用来查找一个字符串的最长回文子串的线性方法,是一个叫 Manacher 的人在 1975 年发明的,这个方法的最大贡献是在于将时间复杂度提升到 ...

  4. manacher马拉车算法

    算法讲解 算法讲解1 #include <iostream> #define maxn 10e+6; using namespace std; char s[maxn],str[maxn* ...

  5. (manacher)马拉车算法专题题目

    manacher算法用来求解回文串问题,时间复杂度为O(n). 不懂的先可以去练习下模板板子题,求最长回文串 传送门P3501 [POI2010]ANT-Antisymmetry 这一题他给的是一个新 ...

  6. Manacher (马拉车)算法

    Manacher于1975年发现了一种线性时间算法,可以在列出给定字符串中从任意位置开 始的所有回文子串.同样的算法也可以在任意位置查找全部极大回文子串,并且时间复杂 度是线性的.那他是怎样实现的呢, ...

  7. 什么是Manacher(马拉车)算法-java代码实现

    截止到目前我已经写了 500多道算法题,其中部分已经整理成了pdf文档,目前总共有1000多页(并且还会不断的增加),大家可以免费下载 下载链接:https://pan.baidu.com/s/1hj ...

  8. Manacher(马拉车算法)

    对应letecode链接: https://leetcode-cn.com/problems/longest-palindromic-substring/ 题目描述: 给你一个字符串 s,找到 s 中 ...

  9. 最长回文 HDU - 3068(求最长回文串的长度【马拉车算法Manacher】)

    马拉车算法 Manacher's Algorithm 是用来查找一个字符串的最长回文子串的线性方法,由一个叫 Manacher 的人在 1975 年发明的,这个方法的最大贡献是在于将时间复杂度提升到了 ...

  10. 最长回文串 马拉车算法 C++

    最长回文串 LeetCode 5.最长回文串 给你一个字符串 s,找到 s 中最长的回文子串. 示例 1: 输入:s = "babad" 输出:"bab" 解释 ...

最新文章

  1. 一年75次上微博热搜!宇宙首富Tony老师上线!马斯克DIY发型
  2. java回退一格_api短信接口
  3. Python爬虫常用模块
  4. 图像中某点绕点旋转后的坐标,图像旋转坐标位置
  5. vue.js分页组件(新手学习记录)
  6. BugkuCTF-WEB题程序员本地网站
  7. 开发整理笔记Markdown基本使用
  8. 开源 区块链_区块链如何补充开源
  9. MySQL8.0.11的安装和Navicat连接mysql
  10. php diff 文本比较,php文本操作方法集合比较
  11. Vue引入Froala-Editor富文本编辑器
  12. python导入表格数据混乱_Python按行打乱Excel表格数据
  13. 北京企业平均薪酬达16.68万元;小米 11 内核已开源;阿里达摩院 2021 十大科技趋势 | EA周报...
  14. 去掉wap端手机浏览器头部搜索栏和底部工具栏的方法
  15. ubuntu命令行fdisk扩展分区
  16. (阿里云)Linux部署springboot项目全过程
  17. android 自定义关机界面,怎么定制Android关机界面
  18. js获取 url 参数值的方法总结
  19. 基于树莓派GPIO口和光电传感器的障碍物检测
  20. goss - 一个简洁的 golang 对象存储库

热门文章

  1. 智慧城管基础业务系统建设系统方案
  2. ISO27001信息安全管理体系证书,系统集成行业企业还有没办的吗?
  3. 条件概率分布、联合概率分布和边缘概率分布
  4. 实用的数据可视化工具大集合
  5. 两台ROS相互通信,并测试节点
  6. Layui treeTable相关
  7. FIL WORLD全球化应用,WORLD量化助推Filecoin生态落地
  8. 仓库管理(WMS)系统及其组成
  9. 支持udp转发的云服务器,云服务器转发udp原理
  10. 测试工具(一)——Abbot 测试SWT