马拉车(manacher) & 回文自动机(PAM)
补充,PAM 的 a[0]=-1,这一点我每次写都要忘记。
读了徐安矣2023年集训队论文写的,对于差分性质和习题,我会在理解清楚之后再补充。本篇博客仅讨论前两种算法。
首先,马拉车和回文自动机都是处理回文串问题的。但在此之前,学习一些更加简单的回文算法。
小 trick:把给定串的两头和缝隙插入相同字符,且在边界处用不同字符标记,使得长度为偶数的回文串和长度为奇数的回文串可以用同一种方式算出来。
例子:
ABBABAB \(\to\) @#A#B#B#A#B#A#B#%
其中的 ABBA \(\to\) #A#B#B#A#,回文中心为中间的 #。ABA \(\to\) #B#A#B#,回文中心为 A。
下文的 \(N\) 表示字符串长度。除了回文自动机使用原串外,其它算法都需要使用在缝隙添加相同字符的新字符串。
- 暴力
对于一个回文中心,枚举回文半径。时间复杂度为 \(O(n^2)\)。
- 哈希
预处理前缀哈希和后缀哈希,可以对于一个回文中心,二分回文半径。具体原理就是如果二分到 Mid,i 左右的长度为 Mid 的段的哈希值相等,那就是回文的,反之则不回文,有二分性。时间复杂度,预处理 \(O(n)\),求单个点的回文半径 \(O(\log n)\)。
- 马拉车
记录当前右端点最大的回文串的回文中心的右端点。初始都是 \(0\)。枚举回文中心,从 \(1\) 到 \(N\)。
如果当前回文中心在右端点之后,那根据每个回文串右端点至少为其本身的性质,当前最右右端点一定为 \(i-1\),则直接暴力更新这个点为回文中心的回文串的右端点,顺带可以算出这个点的回文半径。
否则,根据回文的性质,设当前最右端点为 \(MaxR\),其回文中心为 \(MaxI\),则 \(i\) 可以通过 \(MaxI\) 映射到 \(2MaxI-i\) 这个点,因为这是回文的。之前已经计算过 \(2MaxI-i\) 的回文半径了,但由于在 \(MaxI\) 为回文半径的回文串左端点之前的值与 \(MaxR\) 之后的值不能映射(因为不回文了),若 \(R_i\) 表示 \(i\) 的回文半径,则 \(i\) 点的初始回文半径为 \(\min(R_{2MaxI-i},MaxR-i+1)\)。
定义一下回文半径,若 \(i\) 的回文半径为 \(R_i\),则以 \(i\) 为回文中心的极长回文串为 \([i-R_i+1,r+R_i-1]\),极长的意思就是再长就不行了。
上文的初始回文半径可能并不是最终回文半径,所以暴力拓展,在拓展的同时,顺带更新 \(MaxR\) 即可。因为想要暴力拓展,当且仅当 \(i\) 点的初始回文半径为 \(MaxR-i+1\),此时当前回文串的右端点即为 \(MaxR\),所以 \(MaxR\) 可以跟 \(R_i\) 同时扩展。
考虑时间复杂度,枚举 \(i\) 这部分的时间复杂度为 \(O(N)\),\(MaxR\) 最多只会增加到 \(N\),所以均摊时间复杂度也是 \(O(N)\)。总时间复杂度为 \(O(N)\)。是最快的处理每个点的回文半径的算法。
- 回文自动机
说到这个,我以后还会写 AC 自动机和后缀自动机,优先度很高。如果还有多余的时间,可以写子序列自动机。
回文自动机可以理解成,既是一个 DAG,又是一棵树,其中每个点表示一个回文串。其中 DAG 每条边最多有字符集大小条出边,每条出边表示一个字符。走一条边相当于把一个回文串两边同时加上这条边对应的字符。回文自动机上的每个点的回文串都是原字符串的本质不同回文子串。
为了方便表示,设字符集大小为 \(|S|\)。树上节点 \(u\) 的父亲用 \(Fail_u\) 表示。
首先抛出来一个非常厉害的结论,若在一个串最后添加一个字符,这个串最多只会新增一个回文串。因为如果新增 \(\geq 2\) 个回文串,右端点肯定都是新增那个字符。然后这些回文串的回文中心一定有先后关系,最前面的那个回文串(即最长后缀回文串)可以把后面的回文串给映射到前面去。既然后面是回文的,所以映射到前面去跟后面是相同的,所以后面的回文串一定在前面出现过。所以没出现过的新增回文串只有可能有一个,且为最长后缀回文串。
根据上面这个性质,可以知道,每个字符串的本质不同回文子串的数量级是 \(O(n)\) 的,所以回文自动机的点数和边数有保证。\(Fail_u\) 的实际意义则为:\(u\) 这个回文串的最长回文后缀(除去自己以外的)。
考虑增量法构造回文自动机。已经构造好当前字符串的回文自动机了,新增一个字符 \(c\),然后变成新的回文自动机。开一个数组 \(Length\),其中 \(Length_u\) 表示 \(u\) 这个回文串的长度。方便更新回文自动机。设边的集合为 \(Next\),\(Next_{u,i}\) 表示点 \(u\) 的字符为 \(i\) 的出边连向的点。
初始有两个点,\(0\) 号点,\(Length=0\),表示空回文串,\(1\) 号点,\(Length=-1\),表示左右各添加一个相同字符后成为一个长度为 \(1\) 的回文串。\(Fail_0=1\),方便写代码。用全局变量 \(End\) 表示当前字符串的最长回文后缀,初始为 \(0\)。
新增的点,从最长回文后缀开始跳 \(Fail\),直到遇到一个回文串(设为 \(u\))满足原串中这个回文串左边的第一个字符为 \(c\),这样就满足加入 \(c\) 后,最长回文后缀为那个回文串左右各添加一个 \(c\)。若那个字符串已经有一条 \(c\) 的边了,说明当前没有新增回文串,则 \(End=Next_{u,c}\),没了。否则就新建一个点,再连上这条边。再从 \(Fail_u\) 开始找最长回文后缀满足在原串上左边为 \(c\) 的回文串,设为 \(v\)。则 \(Fail_{new}=Next_{v,c}\)。根据上面的结论,这条边 \(Next_{v,c}\) 一定是存在的。
注意特殊情况,即 \(u\) 为 \(1\) 时,即最长回文后缀就是这个字符本身,那么 \(Fail_{new}=0\)。在写代码时,可以通过全局数组初始值全为 \(0\) 的特性来让代码更简洁,而不需要特判,具体见代码。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN=1e6+50;
char s[MAXN],a[MAXN];
int N;
int tot;
int Next[MAXN][26],Fail[MAXN],Length[MAXN];
int End;
long long Cnt[MAXN];
int GetFail(int u,int Id)
{
while(a[Id-Length[u]-1]!=a[Id])
u=Fail[u];
return u;
}
int main()
{
a[0]=-1;
Length[1]=-1;
Fail[0]=1;
tot=1;
scanf("%s",&s[1]);
N=strlen(s+1);
for(int i=1;i<=N;i++)
{
a[i]=s[i]-'a';
int Last=GetFail(End,i);
if(Next[Last][a[i]]==0)
{
tot++;
Fail[tot]=Next[GetFail(Fail[Last],i)][a[i]];
Length[tot]=Length[Last]+2;
Next[Last][a[i]]=tot;
}
End=Next[Last][a[i]];
Cnt[End]++;
}
long long Max=0;
for(int i=tot;i>=2;i--)
{
Max=max(Max,1ll*Length[i]*Cnt[i]);
Cnt[Fail[i]]+=Cnt[i];
}
printf("%lld",Max);
}
时间复杂度为 \(O(N)\),因为跳 \(Fail\) 可以看做,每次使得深度加一,然后再使得深度变浅很多,所以也是 \(O(N)\) 级别的。
可以快速得到的东西很多,比如想要知道当前字符串的本质不同回文子串数量,则为除了 \(0\) 和 \(1\) 这两个点之外的点数。遍历一遍即可得到所有回文串,得到回文中心和半径也就很简单了。
马拉车(manacher) & 回文自动机(PAM)的更多相关文章
- 4.22 省选模拟赛 三元组 manacher 回文自动机
容易发现可以枚举j 那么只需要计算出 l~j这段是回文串的l的和 以及j+1~r这段是回文串的r的和. 可以manacher 之后想要求出以j为右端点的回文串左端点的和 这个东西我们通过某个点为中心的 ...
- 回文自动机pam
目的:类似回文Trie树+ac自动机,可以用来统计一些其他的回文串相关的量 复杂度:O(nlogn) https://blog.csdn.net/Lolierl/article/details/999 ...
- 回文树/回文自动机(PAM)学习笔记
回文树(也就是回文自动机)实际上是奇偶两棵树,每一个节点代表一个本质不同的回文子串(一棵树上的串长度全部是奇数,另一棵全部是偶数),原串中每一个本质不同的回文子串都在树上出现一次且仅一次. 一个节点的 ...
- 回文树(回文自动机PAM)小结
回文树学习博客:lwfcgz poursoul 边写边更新,大概会把回文树总结在一个博客里吧... 回文树的功能 假设我们有一个串S,S下标从0开始,则回文树能做到如下几点: 1.求串S前缀0~ ...
- BZOJ 3676 [Apio2014]回文串 (后缀自动机+manacher/回文自动机)
题目大意: 给你一个字符串,求其中回文子串的长度*出现次数的最大值 明明是PAM裸题我干嘛要用SAM做 回文子串有一个神奇的性质,一个字符串本质不同的回文子串个数是$O(n)$级别的 用$manach ...
- 回文自动机(PAM) 入门讲解
处理回文串,Manacher算法也是很不错,但在有些问题的处理上比较麻烦,比如求本质不同的子串的数量还需要结合后缀数组才能解决.今天的们介绍一种能够方便的解决关于回文串的问题的算法--PAM. 一些功 ...
- 洛谷P5496 回文自动机【PAM】模板
回文自动机模板 1.一个串的本质不同的回文串数量是\(O(n)\)级别的 2.回文自动机的状态数不超过串长,且状态数等于本质不同的回文串数量,除了奇偶两个根节点 3.如何统计所有回文串的数量,类似后缀 ...
- 【回文自动机】bzoj3676 [Apio2014]回文串
回文自动机讲解!http://blog.csdn.net/u013368721/article/details/42100363 pam上每个点代表本质不同的回文子串.len(i)代表长度,cnt(i ...
- 省选算法学习-回文自动机 && 回文树
前置知识 首先你得会manacher,并理解manacher为什么是对的(不用理解为什么它是$O(n)$,这个大概记住就好了,不过理解了更方便做$PAM$的题) 什么是回文自动机? 回文自动机(Pal ...
- 洛谷P4287 [SHOI2011]双倍回文(回文自动机)
传送门 听说有大佬用manacher$O(n)$过此题……太强啦…… 说一下PAM的做法吧.(看了题解之后发现)蛮简单的 我们肯定要先建出回文自动机的 然后如果是枚举每一个节点暴跳fail指针肯定得T ...
随机推荐
- QGIS 导入文本数据(WKT)
在做GIS数据处理的时候,经常会遇到原始数据是 text.csv.Excel 等格式的数据.要使用数据前提是要先转换数据. 这里是介绍用 QGIS 导入数据.打开导入方式如下(根据自己的文本类型选择不 ...
- 数仓如何进行表级控制analyze?
摘要: 介绍如何设置采样大小和表级控制analyze. 本文分享自华为云社区<GaussDB(DWS) 如何表级控制analyze>,作者:leapdb. 一.控制采样大小 [设置全局采样 ...
- 用BingGPT写一首勉励自己的诗
觉得写的还挺有意思,所以记录一下,祝自己在今后的生活中努力学习,学有所成 勤学不辍志,博览群书知. 海纳百川理,山高自有路. 勿以时日长,惟以功夫深.
- kubernetes核心实战(三)--- ReplicationController
5.ReplicationController ReplicationController 确保在任何时候都有特定数量的 Pod 副本处于运行状态.换句话说,ReplicationController ...
- 【LeetCode动态规划#06】分割等和子集(01背包问题一维写法实战)
分割等和子集 分割等和子集 给你一个 只包含正整数 的 非空 数组 nums .请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等. 示例 1: 输入:nums = [1,5,11,5 ...
- 安全测试前置实践1-白盒&黑盒扫描
作者:京东物流 陈维 一.引言 G.J.Myers在<软件测试的艺术>中提出:从心理学角度来说,测试是一个为了寻找错误而运行程序的过程. 那么安全测试则是一个寻找系统潜在安全问题的过程,通 ...
- React中图片的相对路径引入和绝对路径引入
React中当在JSX中的img标签中引入时使用相对路径引入,地址是基于index.html的而不是当前jsx文件的,如 <img src="./src/assets/images/g ...
- Springboot3整合使用ja-captcha行为验证码解决方案
截止到目前,Springboot最新稳定版本已经迭代到3.0.5,而我们项目中使用的行为验证码框架ja-captcha还没有适配Springboot3,码云上类似的请求也没有得到过回应,于是决定自己动 ...
- Callback详解
Callbacks Callback Registration 在 Rails 中,回调(Callbacks)是一种在模型对象的生命周期中执行特定代码的机制.回调可以在模型对象的创建.更新.删除等操作 ...
- 一文搞懂JavaScript数组的特性
前言 数组是几乎所有编程语言的基础语法,JavaScript因为语法特性,之前缺少一些集合类对象,对数组的使用就会更多一些,因此我们更需要理解数组知识. 然而大部分人对数组都已经非常熟悉了,所以本文将 ...