概要

一个字符串有多少个回文的字串?最多有 \(O(n^2)\) 级别个。但 Manacher 算法却可以用 \(O(n)\) 的时间复杂度解决这个问题。同时 Manacher 算法实现非常简单。

一个显而易见的结论是:如果 \(S_{1\cdots n}\) 是回文串,那么 \(S_{2\cdots n-1}\) 也是回文串。

根据这一个性质,我们可以得到 \(O(n^2)\) 的暴力的做法:

以 \(i\) 为中心,向两侧暴力扩展,得到所有以 \(i\) 位中心的回文串。这些回文串长度为奇数。

以 \(i\) 和 \(i+1\) 为中心,向两侧暴力扩展,得到所有以 \(i\) 和 \(i+1\) 为中心的回文串。这些回文串的长度为偶数。

实际上,为了方便实现,可以在两个字符间和首尾插入空字符。这样所有的回文串的长度都变为奇数。下面默认使用了这种方法,所有回文串长度为奇数,下标从 \(1\) 开始。

Manacher 充分利用了回文的性质,构造出令人惊叹的巧妙做法:

令 \(d_i\) 表示以 \(i\) 为中心的回文串最大长度的一半,那么只要求得 \(d\) ,就可以知道所有回文串的信息。

不妨令 \(l,r\) 表示当前考虑到的回文串中 右端点最靠右 的那个回文串。初始时不妨令 \(l=r=0\) 。

从左到右枚举回文中心 \(i\) 。如果 \(i > r\) ,那么调用暴力算法求得 \(d_i\) 。否则可以找到回文串 \(S_{l\cdots r}\) 中与 \(i\) 对称的位置 \(j=l+(r-i)\) 。此时,如果 \(i+d_j < r\) ,那么根据 \(S_{l\cdots r}\) 的对称性, \(d_i=d_j\) 。否则的话, 由于无法保证 \(r\) 之后与 \(l\) 之前的对称性,先令 \(d_i=r-i\) ,再在此基础上执行暴力算法。

最后不要忘记更新 \(l,r\) 。

不难发现, \(r\) 是不减的。而暴力算法中向两侧暴力拓展的次数不超过 \(r\) 增加的值。所以 Manacher 算法中,暴力的部分均摊是 \(O(n)\) 的。外层循环也是 \(O(n)\) ,那么总的时间复杂度就是 \(O(n)\) 的。

luogu4555

\(S\) 长度 \(1e5\) 我线段树一只 \(log\) T 了?这个评测机略微有点快啊……不过可以 \(O(n)\) 的……

在 Manacher 之后,可以 \(O(n)\) 预处理出对于每个位置 \(i\) 为结尾的最长回文串和以 \(i\) 为开始的最长回文串。通过加入的空字符统计答案即可。注意必须要是两个回文串,所以单一一个空字符不能算作回文。

#include <cstdio>
#include <cstring>
#include <algorithm> const int Maxn = 100010;
const int INF = 1e9;
char Ch[Maxn << 1];
int D[Maxn << 1];
int n, Ans, L[Maxn << 1], R[Maxn << 1]; inline void Manacher(); int main() {
scanf("%s", Ch + 1);
n = strlen(Ch + 1);
for (int i = n; i >= 1; --i) Ch[i << 1] = Ch[i];
for (int i = 0; i <= n; ++i) Ch[i << 1 | 1] = '_';
n = n << 1 | 1;
Ch[0] = '*', Ch[n + 1] = '\0';
Manacher();
for (int i = 1; i <= n; ++i) {
L[i + D[i]] = std::max(L[i + D[i]], D[i]);
R[i - D[i]] = std::max(R[i - D[i]], D[i]);
}
for (int i = 1; i <= n; ++i)
if (i & 1)
R[i] = std::max(R[i], R[i - 2] - 2);
for (int i = n; i >= 1; --i)
if (i & 1)
L[i] = std::max(L[i], L[i + 2] - 2);
Ans = 0;
for (int i = 1; i <= n; ++i)
if (R[i] && L[i])
Ans = std::max(Ans, R[i] + L[i]);
printf("%d\n", Ans);
return 0;
} inline void Spand(int x) {
for(; Ch[x - D[x] - 1] == Ch[x + D[x] + 1]; ++D[x]);
return;
} inline void Manacher() {
int L = 0, R = 0;
for (int i = 1; i <= n; ++i) {
if (i > R) {
Spand(i);
L = i - D[i], R = i + D[i];
continue;
}
int Ops = L + (R - i);
if (i + D[Ops] >= R) {
D[i] = R - i;
Spand(i);
L = i - D[i], R = i + D[i];
continue;
}
D[i] = D[Ops];
}
return;
}

Manacher 算法学习小记的更多相关文章

  1. Manacher算法学习笔记 | LeetCode#5

    Manacher算法学习笔记 DECLARATION 引用来源:https://www.cnblogs.com/grandyang/p/4475985.html CONTENT 用途:寻找一个字符串的 ...

  2. Gcd&Exgcd算法学习小记

    Preface 对于许多数论问题,都需要涉及到Gcd,求解Gcd,常常使用欧几里得算法,以前也只是背下来,没有真正了解并证明过. 对于许多求解问题,可以列出贝祖方程:ax+by=Gcd(a,b),用E ...

  3. Manacher算法学习 【马拉车】

    好久没写算法学习博客了 比较懒,一直在刷水题 今天学一个用于回文串计算问题manacher算法[马拉车] 回文串 回文串:指的是以字符串中心为轴,两边字符关于该轴对称的字符串 ——例如abaaba 最 ...

  4. manacher算法学习(求最长回文子串长度)

    Manacher总结 我的代码 学习:yyb luogu题目模板 xzy的模板 #include<iostream> #include<cstdlib> #include< ...

  5. Manacher算法学习笔记

    前言 Manacher(也叫马拉车)是一种用于在线性时间内找出字符串中最长回文子串的算法 算法 一般的查找回文串的算法是枚举中心,然后往两侧拓展,看最多拓展出多远.最坏情况下$O(n^2)$ 然而Ma ...

  6. Manacher 算法学习笔记

    算法用处: 解决最长回文子串的问题(朴素型). 算法复杂度 我们不妨先看看其他暴力解法的复杂度: \(O(n^3)\) 枚举子串的左右边界,然后再暴力判断是否回文,对答案取 \(max\) . \(O ...

  7. Cipolla算法学习小记

    转自:http://blog.csdn.net/doyouseeman/article/details/52033204 简介 Cipolla算法是解决二次剩余强有力的工具,一个脑洞大开的算法. 认真 ...

  8. 学习笔记 - Manacher算法

    Manacher算法 - 学习笔记 是从最近Codeforces的一场比赛了解到这个算法的~ 非常新奇,毕竟是第一次听说 \(O(n)\) 的回文串算法 我在 vjudge 上开了一个[练习],有兴趣 ...

  9. 二次剩余Cipolla算法学习笔记

    对于同余式 \[x^2 \equiv n \pmod p\] 若对于给定的\(n, P\),存在\(x\)满足上面的式子,则乘\(n\)在模\(p\)意义下是二次剩余,否则为非二次剩余 我们需要计算的 ...

随机推荐

  1. ASP.NET-A low-level Look at the ASP.NE

    请求处理模型1: ******** 1.浏览器向服务器发送请求,先到达服务器的http.sys系统文件,进行初步的处理. (服务器分为内核模式和用户模式,http.sys在内核模式种,IIS在用户模式 ...

  2. 守护服务Supervisor的安装和使用

    Supervisor(http://supervisord.org/)是用Python开发的一个client/server服务,是Linux/Unix系统下的一个进程管理工具,不支持Windows系统 ...

  3. springboot-异步、发送邮件(二)

    @Test //发送复杂邮件 void contextLoads2() throws MessagingException { MimeMessage mimeMessage = mailSender ...

  4. VisualVM的使用

    1.解压压缩包(如visualvm143.zip) 2.修改etc/visualvm.conf 中的visualvm_jdkhome配置 3.双击bin/visualvm.exe 4.安装插件,可能一 ...

  5. C# list to dictionary

    示例: 新建一个类: public class Lang { public string En; public string Ch; } 实例化并转为字典: List<Lang> lang ...

  6. C# 校验车架号(VIN码)第9位是否有效算法

    public static bool checkVIN(string vin) { //VIN码从第1位到第17位的“加权值”: Dictionary<int, int> vinMapWe ...

  7. 在vue中引用echarts导致Cannot read property getAttribute of null ?

    报错信息如下: 之前一直用echarts没有出现过这个问题,所以当这个问题出现时我就开始了各种查,试了几种方法依旧报错,比如: 1.在mounted() {},写成如下形式:(依旧报错) this.$ ...

  8. DX使用随记--其他

    1.  百分号显示格式 百分号:{0:P}表示显示为百分号模式.如数据源中为0.5.表示出来为50%

  9. rabbimq 生产消费者

    composer.json { "require": { "php-amqplib/php-amqplib": "^2.9" } } com ...

  10. 使用python下载图片(福利)

    刚学python 没多久, 代码处处是漏洞,也希望各位大佬理解一下 爬出来的图片... 使用的 是 https://www.tianapi.com/  接口下的 美女图片... (需要自己注册一个账号 ...