回文自动机 PAM

news/2025/2/24 17:23:56/文章来源:https://www.cnblogs.com/laoshan-plus/p/18732960

回文自动机 PAM

约定字符串下标从 \(0\) 开始。

定义

回文自动机,又称回文树,是一种 2014 年才发表的新算法。顾名思义,回文自动机用于求解回文串问题。它相较于 Manacher 算法的优点在于支持在线修改且复杂度不变。

实现

回文自动机的关键技术可以概括为 “奇偶字典树 + 后缀链跳跃”

后缀链跳跃

考虑如何设计一种高效的求回文串的算法。Manacher 采用了递推,从 \(P(0)\cdots P(i-1)\) 递推到 \(P(i)\)。那么放到回文自动机上,也可以试图动态地加入一个个字符,判断它是否能和前面的字符形成回文。显然,如果新字符能产生回文,一定是和前面的后缀一起,并且应该从尽可能远的地方开始检查,才能节省时间复杂度。同时,如果当前字符是 \(i\),在前方不远处有一个 \(j\) 和字符 \(i\) 相同,那么 \([j+1,i-1]\) 这一段区间应该也是一个回文串。

说了这么多,我们应当试图从 \(i\) 前面最长的回文串的位置开始查找,跳向使得 \([j+1,i-1]\) 是一个新的回文串的 \(j\) 的位置,直到发现 \(j=i\)

这个操作,就是后缀链跳跃

奇偶字典树

类似于 AC 自动机的 \(\text{fail}\) 标记,上面所说的后缀链也应当有一个标记,同时在字典树上进行。我们将 PAM 上的这个后缀链也叫做 \(\text{fail}\)。说到字典树,因为要体现回文串之间的包含关系,而字典树的一段路径刚好对应一个子串,所以用字典树是合适的。但因为回文串分奇偶两种,所以需要建两个字典树,分别为奇字典树和偶字典树,根分别设置为 \(0\)\(1\)

字典树上的每个节点的回文串实际上是从它到根再到它这么一个串。每个点需要维护的是这个回文串的长度,以及在字典树上的父亲和儿子。

建立 PAM 是一个动态的过程,考虑到每一个节点代表的回文串一定是某一个位置为结束位置的最长回文串,所以每加入一个字符 \(c\),都可以沿着 \(c\) 在字典树上父亲的 \(\text{fail}\) 标记跳下去,直到满足上文所说的回文性质。对于长度,容易发现 PAM 上儿子的回文串长度是父亲的回文串长度加 \(2\)

最后,对于初始状态来说,我们让偶字典树的根的 \(\text{fail}\) 指针指向奇字典树的根,因为作为空串的长度为奇数的字符串长度可以看作 \(-1\),是长度为 \(0\) 的字符串的后缀。可以认为奇字典树没有 \(\text{fail}\) 指针,因为每一个字符都是一个回文串,所以即使根没有 \(\text{fail}\) 指针也不会失配。

代码(P5496 【模板】回文自动机(PAM))

#include<bits/stdc++.h>
using namespace std;constexpr int MAXN=5e5+5;
string s;
int n,tot=1,lst;
struct PAM{int len,fail,dep,s[26];
}p[MAXN];// 背板!
void init(){p[0].fail=1;p[1].len=-1;
}
int gtf(int x,int i){while(i-p[x].len-1<0||s[i-p[x].len-1]!=s[i]) x=p[x].fail;return x;
}
void ins(int i){int pos=gtf(lst,i),ch=s[i]-'a';if(!p[pos].s[ch]){p[++tot].fail=p[gtf(p[pos].fail,i)].s[ch];p[tot].len=p[pos].len+2;p[tot].dep=p[p[tot].fail].dep+1;p[pos].s[ch]=tot;}lst=p[pos].s[ch];
}int main(){cin.tie(nullptr)->sync_with_stdio(0);cin>>s;n=s.size();init();for(int i=0,ans=0;i<n;i++){s[i]=(s[i]-97+ans)%26+97;ins(i);cout<<(ans=p[lst].dep)<<' ';}cout<<'\n';return 0;
}

复杂度

回文自动机中最耗时的操作是跳 \(\text{fail}\) 指针,这个操作是跳跃进行的,因为可以看作深度每次减少然后加 \(1\) 的过程,最多有 \(N\) 次加操作和 \(N\) 次减操作,所以复杂度还是 \(O(N)\) 的。

应用

结尾回文串个数

题意:给出一个字符串 \(S\),求以每个位置为结尾的回文串个数。

典型例题:P5496 【模板】回文自动机(PAM)、P3649 [APIO2014] 回文串。

注意到我们上面维护的 \(\mathit{dep}\) 值,这个值记录的是每个节点在回文树上的深度,由回文自动机的定义,从它开始跳 \(\text{fail}\) 指针直到根的节点个数就是答案。

所以每插入一个字符输出 p[lst].dep 即可。

双倍回文子串

题意:求一个字符串的最长双倍回文子串长度。

所谓双倍回文,指的是由两个回文串拼起来得到的串,不同题目里的定义各略有不同。

典型例题:P4287 [SHOI2011] 双倍回文,[HDU6599] I Love Palindrome String。

我们已经有了一个 \(\text{fail}\) 指针,现在要新增一个 \(\text{trans}\) 指针,意义为:该节点对应的回文串最长的、满足长度不超过该回文串 \(\boldsymbol{1/2}\) 的后缀回文串对应的节点。相当于在 \(\text{fail}\) 的基础上增加了一个长度限制。

\(\text{trans}\) 的方式和求 \(\text{fail}\) 是类似的。

int gtf(int x,int i){while(i-p[x].len-1<0||s[i-p[x].len-1]!=s[i]) x=p[x].fail;return x;
}
int gts(int x,int i,int len){while(x!=1&&(s[i-p[x].len-1]!=s[i]||p[x].len+2>(len+1)>>1)) x=p[x].fail;return x;
}
void ins(int i){int pos=gtf(lst,i),ch=s[i]-'a';if(!p[pos].s[ch]){p[++tot].fail=p[gtf(p[pos].fail,i)].s[ch];p[pos].s[ch]=tot;p[tot].len=p[pos].len+2;p[tot].trans=p[gts(p[pos].trans,i,p[tot].len)].s[ch];}lst=p[pos].s[ch];p[lst].ans++;
}

那么有了这个 \(\text{trans}\) 指针,我们在建立 PAM 之后挨个检查每个节点是否满足条件就方便很多了。以上面例题的第一道双倍回文为例,我们只需检查每个节点是否满足 \(\text{len}(\text{trans}(i))\times2=\text{len}(i)\land \text{len}(i)\bmod4=0\) 即可。

另外,PAM 还可以辅助做一些 DP 问题,比如 P4762 [CERC2014] Virus synthesis。它的本质还是利用 \(\text{trans}\) 数组找出转移点,再结合情况分类讨论,需要具体问题具体分析。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/889095.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Redis低版本客户端Jedis2.9.0兼容高版本redis(比如redis6)不支持ACL的问题(亲测可用)

redis6版本及以上使用了acl认证 就是通过账号密码去认证,但是在之前版本都是只需要密码的,这种如果要适配,可能就要升级jedis客户端依赖的,但是一些老项目都不能随便升级的 之前的代码可能就不适配了 如果就要考虑使用现有的jedis版本 如:2.9.0 去支撑redis6的连接 以下有…

Linux 中sed命令的整行替换

Linux 中sed命令的整行替换.001、基本用法[root@PC1 test2]# ls a.txt [root@PC1 test2]# cat a.txt ## 测试文件 a UU i a UU i b q j c q y [root@PC1 test2]# sed /b/ s/.*/QQ/ a.txt ## 将匹配b的行全部替换为Q…

高科战神拨号精灵app下载设置方法

高科战神拨号助手、拨号健、全家软件下载,高科拨号精灵app使用设置方法说明:首先用安卓手机安装拨号助手app【联系图片上的V信 2081003456下载】。安装好后拨12345678进行蓝牙连接,蓝牙连接好后就可以进行操作。1拨号 2拨号 3拨号 4拨号 5拨号 关闭程序 12345678拨号设置。

Spherical Linear Interpolation and Text-Anchoring for Zero-shot Composed Image Retrieval

目录概Spherical Linear Interpolation (Slerp)Text-Anchored-Tuning (TAT)代码Jiang Y. K., Huynh D., Shah A., Chen W. and Lim S. Spherical linear interpolation and text-anchoring for zero-shot composed image retrieval. ECCV, 2024.概 本文提出了一种非常简单的 Ze…

uniapp + 微信小程序:新版canvas常用api及注意事项

关于新旧canvas的比较我以前写过一篇博客 :https://www.cnblogs.com/sunshine233/p/17014701.html ,这里就不重复了。 但在正文开始之前,我不得不再说一遍微信的文档写的真垃圾。很多问题的答案都是在微信开发者社区里找到的。一、新版canvas 基础用法:<template><…

Java 实现 Excel(XLS/ XLSX)和 HTML 格式之间的转换

Excel 是一种电子表格格式,广泛用于数据处理和分析,而HTM则是一种用于创建网页的标记语言。虽然两者在用途上存在差异,但有时我们需要将数据从一种格式转换为另一种格式,以便更好地利用和展示数据。本文将介绍如何通过 Java 实现 Excel 与 HTML 格式之间的相互转换。将Exce…

Python异步编程终极指南:用协程与事件循环重构你的高并发系统

title: Python异步编程终极指南:用协程与事件循环重构你的高并发系统 date: 2025/2/24 updated: 2025/2/24 author: cmdragon excerpt: 🚀 深入剖析Python异步编程的核心机制。你将掌握:\n 事件循环的底层实现原理与调度算法\n async/await协程的6种高级用法模式\n 异步H…

Uniapp开发安卓app之使用360加固加壳处理

Uniapp开发安卓app之使用360加固加壳处理 注:360加固只针对Android App免费。360加固助手官方下载 1.流程 安卓加固 》 签名APK 2.前提 先使用Hbuilder完成app打包,得到apk文件。 3.安卓加固1)安装、登录360加固软件并打开 2)在安卓加固》APK加固》添加任务,选择hbi…

用python画五角星

import turtle turtle.setup(500,500) turtle.bgcolor("white") turtle.pensize(3) turtle.color("red") turtle.begin_fill() for _ in range(5): turtle.forward(200) turtle.right(144) turtle.end_fill() turtle.hideturtle() turtle.done()

win10忘记开机密码怎么办?

下面我们来看看win10笔记本电脑开机密码忘记了怎么办_ 方法一、重置电脑,但是数据可能会丢失 1、开机,在看到Windows10Logo,下面有个圆圈在转的时候,数5秒。然后直接按下电源键关机。2.桌面,如果您的台式机有重新启动键,也可以直接按重新启动键。这样重复2~3次。 三、鼠标…