【学习笔记】回文自动机初步总结

news/2025/10/16 16:54:42/文章来源:https://www.cnblogs.com/LinkyChristian/p/19146071

什么是PAM啊(战术后仰)

某日看到同学写了道回文自动机题的题解,于是一时兴起学了回文自动机并打了两道题。结果过了几周就忘了,甚至网课上老师提到PAM我以为是后缀自动机。现在想来甚是珂怕,故作此文。

回文自动机(PAM)是一种用于存储字符串回文信息的数据结构。

回文串分为奇回文串和偶回文串,因此我们开两棵树来存储它们,并在后继的描述中将其称作奇树和偶树。

回文自动机的构建

每个回文自动机节点存储着一个值 $len_i$ 表示这个点所代表回文串的长度,设当前节点为 $now$ ,那么 $tr_{now,i}$ 表示在 $now$ 节点所代表的回文串的前后各加一个字符 $i$ 所构成的回文串。显然,从根到 $now$ 的链保存了 $now$ 所代表的回文串的信息。

$fail : fail$ 指针存储着该节点所对应的 最长回文后缀 所对应的节点(显然最长回文后缀也一定在这棵树上)。

初始状态下,我们有两个节点,奇树和偶树的根,它们的 $len$ 分别是 $-1,0$ 。其中偶数的根的 $fail$ 指向奇数的根。

假设我们已经构建完了 $1 \sim i-1$ 的回文自动机,考虑加入第 $i$ 个字符。

  1. 找到 $1 \sim i-1$ 的 最长后缀回文串 对应的节点 $lst$ (在没有加入字符时 $lst$ 默认为偶树的根)。

  2. 尝试在 $lst$ 的前后加上字符 $i$ ,如果 $tr_{lst,i}$ 已经存在,则将 $tr_{lst,i}$ 设置为 $lst$ ,准备匹配第 $i+1$ 个字符。

  3. 如果 $tr_{lst,i}$ 为空,那我们需要构建该节点与其 $fail$ 指针。如果 $lst$ 所对应回文串的左端点的前一个字符与 $i$ 相同,那么新建一个节点 $tr_{lst,i}$ 表示以 $i$ 为右端点的最长回文串。否则找到 $lst$ 节点的 $fail$ (其同样是 $1 \sim i-1$ 的 后缀回文串),并重复上述操作。直到找到一个后缀回文串能在前后同时加上 $i$ ,或者找到偶树或奇树的根(代表此时没有已有回文串匹配)。设此时找到的节点为 $p$ ,新建一个节点 $tr_{p,i}$ 表示以 $i$ 为右端点的最长回文串,并将 $len_{tr_{p,i}}$ 设置为 $len_p +2$ (此时将奇数的根的 $len$ 设置为 $-1$ 的用处体现出来了,奇数的根下面是单个字符,其长度为 $1$)。

  4. 继续从 $p$ 往后跳 $fail$ ,直到找到另一个能在前后同时加上 $i$ 的节点, 并将在其前后加上 $i$ 的节点(此处这个节点一定存在,可以尝试根据回文性质自行证明)作为 $tr_{p,i}$ 的 $fail$ 。

  5. 将 $tr_{p,i}$ 设置为 $lst$ ,准备匹配第 $i+1$ 个字符。

例题:

P3649 [APIO2014]回文串

构建回文自动机,计算每个回文串作为最大后缀回文串出现的次数,再向每个回文串的 $fail$ 贡献出现次数即可。


#include<bits/stdc++.h>
#define N 300010
#define int long long
using namespace std;
char s[N];
int n,ch[N][26],las,p,q,cnt[N*26],len[N*26],fail[N*26],tot;
void init() {s[0]=-1,fail[0]=1,las=0;len[0]=0,len[1]=-1,tot=1;memset(ch,0,sizeof(ch));
}
int nw(int x) {len[++tot]=x;return tot;
}
int getfail(int x,int pos) {while(s[pos-len[x]-1]!=s[pos]) x=fail[x];return x;
}
void ins(int c,int i) {p=getfail(las,i);if(!ch[p][c]) {q=nw(len[p]+2);fail[q]=ch[getfail(fail[p],i)][c];ch[p][c]=q;}cnt[las=ch[p][c]]++;
}
int ans=0;
signed main()
{scanf("%s",s+1),n=strlen(s+1);init();for(int i=1; i<=n; i++) ins(s[i]-'a',i);for(int i=tot; i>=1; i--) cnt[fail[i]]+=cnt[i];for(int i=1; i<=tot; i++) ans=max(ans,cnt[i]*len[i]);printf("%lld\n",ans);return 0;
}

P4762 [CERC2014]Virus synthesis

利用回文自动机查询每个回文串 长度在其一半及以下 的后缀回文串并dp即可。

$Code$

#include<bits/stdc++.h>
#define N 200010
#define sz sizeof(int)*5
using namespace std;
char s[N];
int T,n,val[105],tot;
int dp[N],len[N],fail[N],ch[N][5];
int trans[N],last,p,q,str[N],ans,que[N];
void init() {val['A']=0,val['C']=1,val['G']=2,val['T']=3;s[0]=-1,fail[0]=1,last=0;len[0]=0,len[1]=-1,tot=1;memset(ch[0],0,sz),memset(ch[1],0,sz);
}
int nw(int x) {len[++tot]=x;memset(ch[tot],0,sz);return tot;
}
int getfail(int x,int n) {while(s[n-len[x]-1]!=s[n]) x=fail[x];return x;
}
void ins(int c,int i) {p=getfail(last,i);if(!ch[p][c]) {q=nw(len[p]+2);fail[q]=ch[getfail(fail[p],i)][c];ch[p][c]=q;if(len[q]<=2) trans[q]=fail[q];else {int tmp=trans[p];while(s[i-1-len[tmp]]!=s[i]||(len[tmp]+2)*2>len[q]) tmp=fail[tmp];trans[q]=ch[tmp][c];}}last=ch[p][c];
}
int main()
{scanf("%d",&T);while(T--) {scanf("%s",s+1);init();ans=n=strlen(s+1);for(int i=1; i<=n; i++) ins(val[s[i]],i);for(int i=2; i<=tot; i++) dp[i]=len[i];int head=1,tail=0;que[++tail]=0,dp[0]=1;while(head<=tail) {int now=que[head++];for(int i=0; i<4; i++) {int x=ch[now][i];if(!x) continue;dp[x]=dp[now]+1;int y=trans[x];dp[x]=min(dp[x],dp[y]+1+len[x]/2-len[y]);ans=min(ans,dp[x]+n-len[x]);que[++tail]=x;}} printf("%d\n",ans);}return 0;} 

P4287 [SHOI2011]双倍回文

与上一题类似,对每个回文串记录其长度小于等于其一半的最长后缀回文串,如果其长度恰好为该串的一半,且长度为偶数,向答案贡献即可。

// Problem: P3649 [APIO2014]回文串
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3649
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)#include<bits/stdc++.h>
#define N 500010
#define int long long
using namespace std;
char s[N];
int n,ch[N][26],las,p,q,cnt[N*26],len[N*26],fail[N*26],tot,trans[N*26];
void init() {s[0]=-1,fail[0]=1,las=0;len[0]=0,len[1]=-1,tot=1;memset(ch,0,sizeof(ch));
}
int nw(int x) {len[++tot]=x;return tot;
}
int getfail(int x,int pos) {while(s[pos-len[x]-1]!=s[pos]) x=fail[x];return x;
}
void ins(int c,int i) {p=getfail(las,i);if(!ch[p][c]) {q=nw(len[p]+2);fail[q]=ch[getfail(fail[p],i)][c];ch[p][c]=q;if(len[q]<=2) trans[q]=fail[q];else {int tmp=trans[p];while(s[i-1-len[tmp]]!=s[i]||(len[tmp]+2)*2>len[q]) tmp=fail[tmp];trans[q]=ch[tmp][c];}}cnt[las=ch[p][c]]++;
}
int ans=0;
signed main()
{scanf("%lld%s",&n,s+1);init();for(int i=1; i<=n; i++) ins(s[i]-'a',i);for(int i=2; i<=tot; i++) if(len[trans[i]]*2==len[i]&&len[trans[i]]%2==0) ans=max(ans,len[i]);printf("%lld\n",ans);return 0;
}

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

从正序和倒序建两个回文自动机,记录以 $i$ 为右端点的最长后缀回文串和以 $i$ 为左端点的最长前缀回文串,枚举分割点即可。

$Code$

//2018.11.21 by ljz
#include<bits/stdc++.h>
using namespace std;
#define res register int
#define LL long long
#define inf 0x3f3f3f3f
#define eps 1e-15
inline int read(){res s=0;bool w=0;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')w=1;ch=getchar();}while(ch>='0'&&ch<='9')s=s*10+ch-'0',ch=getchar();return w?-s:s;
}
inline void _swap(res &x,res &y){x^=y^=x^=y;
}
inline int _abs(const res &x){return x>0?x:-x;
}
inline int _max(const res &x,const res &y){return x>y?x:y;
}
inline int _min(const res &x,const res &y){return x<y?x:y;
}
const int N=1e5+10;
namespace MAIN{int n;int a[N],b[N];struct PAM{struct Pam{int vis[26],len,fail;}pam[N];int las,cnt;PAM() {pam[1].fail=pam[0].fail=1,pam[cnt=1].len=-1;}inline void extend(const res &x,const res &id,char *str){res p=las;for(;str[id-pam[p].len-1]!=str[id];p=pam[p].fail);if(!pam[p].vis[x]){res np=++cnt,q=pam[p].fail;for(;str[id-pam[q].len-1]!=str[id];q=pam[q].fail);pam[np].fail=pam[q].vis[x],pam[p].vis[x]=np,pam[np].len=pam[p].len+2;}las=pam[p].vis[x];}}A,B;char str[N];int ans;inline void MAIN(){scanf("%s",str+1);n=strlen(str+1);for(res i=1;i<=n;i++)A.extend(str[i]-'a',i,str),a[i]=A.pam[A.las].len;reverse(str+1,str+n+1);for(res i=1;i<=n;i++)B.extend(str[i]-'a',i,str),b[n-i+1]=B.pam[B.las].len;for(res i=1;i<n;i++)ans=_max(a[i]+b[i+1],ans);printf("%d\n",ans);}
}
int main(){MAIN::MAIN();return 0;
}

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

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

相关文章

完整教程:LeetCode算法日记 - Day 58: 目标和、数组总和

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

【学习笔记】回滚莫队初步总结

什么是回滚莫队啊(战术后仰) 首先确定一个常数$len$(一般而言取 $\sqrt{n}$ ,$n^{\frac{2}{3}}$),将数组分为$\lceil {\frac{n}{len}} \rceil$个块 对于$m$个区间询问,将其按照左端点$l$所属的块从小到大排序,然后…

MATLAB中基于 S-V模型进行毫米波信道建模与仿真

MATLAB中基于Saleh-Valenzuela(S-V)模型进行毫米波信道建模与仿真 S-V模型能较好地描述毫米波信道中的多径成分以簇(cluster)形式到达的特性。毫米波频段(30-300 GHz)的信号传播具有显著的路径损耗和大气衰减(例…

python之模块

python之模块 一、模块的介绍组织python 1、python模块,是一个python文件,一个.py文件,半酣python对象定义和poytho语句 2、模块能够有逻辑的组织代码 3、把相关的代码分配到一个模块里能让你的代码更好用,更易懂。…

2025 年电动阀门厂推荐榜:电动/气动/高压/真空阀门厂,上海巨良阀门凭技术与口碑领跑行业

在《“十四五” 智能制造发展规划》等政策推动下,工业自动化升级加速,阀门作为流程工业的 “神经中枢”,其智能化、可靠性需求在石油化工、新能源、核工业等多领域持续攀升。2025 年中国控制阀市场规模预计突破 500…

认知与困境

我的问题是,我对身边的人。对他们的认知,好像有问题。还有一些社会的规则,是我对他们太信认了。我之所以有这样的感觉是因为,我最近的改变是从。我从那本治疗焦虑的书上的一些内容开始的。书上面介绍了美国精神类的…

【学习笔记】线性基

模拟赛遇到一个笨比线性基题但是忘了怎么求编号......最后因读入T掉来不及修改赛后20s过题,惨痛教训。 线性基是一组极小的用于表达一个数集异或空间的基底。其满足以下性质: 1.能由原数集异或得到的任意一个数,线性…

rest_framework框架视图集整理

from rest_framework.generics import GenericAPIView from rest_framework.request import Request from rest_framework import filters from rest_framework import mixins from rest_framework import generics fr…

x86_64架构__rdtsc指令

__rdtsc指令用于读取处理器的时间戳计数器,该计数器计算自上次复位以来的时钟周期数。这对性能监控和基准测试非常有用。 以下示例为linux环境上Demo#include <stdio.h> #include <x86intrin.h> #include…

AT_joisc2021_c フードコート (Foodcourt)

换维扫描线好题。 发现删除操作会出现过删(即删除的个数大于序列中元素数),这个很难处理。 思考一下应该能想到将每次询问查询的 \(b\to cnt_{del}+b\) 其中 \(cnt_{del}\) 为在该询问之前这个序列删除的数的个数,…

SPP question regarding Issues Due To Gaming Spoofers

Checking SPP Registry Key [Incorrect ModuleId Found]在windows下遇到该问题时,或许可以考虑导入如下的注册表文件。 下载 SPP ref: Issues Due To Gaming Spoofers

类型安全ORM的高并发场景解决方案 - 实践

类型安全ORM的高并发场景解决方案 - 实践pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "M…

L06_mybatis读取MySQL数据库(懵逼版)

由于上课完全听不懂,又不会java,经过三天折磨,查阅各种网站,总算解决了,一个,,,,,小小小小作业。 1.创建一个项目,如果IDEA为2024版,左上角File-》new-》Project按照上面图片配置就好了。 2.需要加载的依赖…

提供给第三方接口的验证方法

第三方调用(认证方式时效限制5分钟)该 postman 调用 url 为样例 url请求头: AuthorizationBasicMTIzOmRjOTg0ZDY4NzJjNWZkYWFkZDllZDg4ZGE5ZmU5ZDYwZDc40TkxMzA0NWFmNjhkMjNiMzE4NzUzZTRhYTIWMDU= 请求头: Timestamp20…

【2025最新】6款免费DLL修复工具推荐:彻底解决“XXX.dll缺失”问题!

系统频繁弹出“XXX.dll缺失”提示?本文汇总6款2025年最新免费DLL修复工具,包括金山毒霸电脑医生、迅捷DLL助手、360安全卫士等,一键扫描自动修复系统缺失DLL,支持Win7/10/11,让你的电脑秒恢复稳定运行。电脑运行时…

2025 年注浆管生产厂家最新推荐排行榜:聚焦 0.3mm 精度与国企合作案例,助力基建企业精准挑选优质供应商

在基建工程中,注浆管是保障防渗加固、隧道支护等环节安全的关键材料,其质量直接决定工程使用寿命与施工安全。当前市场上,部分厂家为压缩成本,选用低强度钢材、忽视品控,导致管材抗压不足、壁厚不均,频繁出现破裂…

vue 下拉框 vxe-select 实现人员选择下拉列表

vue 下拉框 vxe-select 实现人员选择下拉列表 使用自定义选项插槽的方式,可以灵活的实现各种类型的下拉选择 官网:https://vxeui.com github:https://github.com/x-extends/vxe-pc-ui gitee:https://gitee.com/x-e…

双核A53+开源生态,ZX7981P Wi-Fi6 5G插卡路由器开发板CPE内核新选择!

双核A53+开源生态,ZX7981P Wi-Fi6 5G插卡路由器开发板CPE内核新选择!在物联网技术飞速发展的今天,一款出色的开发平台能够极大加速产品研发进程。启明智显推出的ZX7981P Wi-Fi6 5G插卡路由器开发板,正是为高效开发…

2025 高效过滤器制造企业最新推荐榜:供货商定制方案深度解析及口碑评级

在环保水处理与空气净化行业升级提速的背景下,精密过滤器的定制适配性直接决定项目达标效率与运营成本。当前市场呈现 “两极分化” 态势:老牌企业虽经验丰富,但部分定制方案创新不足;新兴品牌虽灵活度高,却因缺乏…

最后防线 解题报告

简要题意 给定一棵 \(n\) 个节点的有根树,每个点有点权 \(a\)。求出一个访问顺序,使得所有点都在其祖先之后被访问,设第 \(i\) 个节点是第 \(p_i\) 个被访问到的,最小化 \(\sum \limits_{1\ le i \le n}p_ia_i\)。…