当前位置: 首页 > news >正文

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

什么是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.hskmm.com/?act=detail&tid=32434

相关文章:

  • 【学习笔记】回滚莫队初步总结
  • MATLAB中基于 S-V模型进行毫米波信道建模与仿真
  • python之模块
  • 2025 年电动阀门厂推荐榜:电动/气动/高压/真空阀门厂,上海巨良阀门凭技术与口碑领跑行业
  • 认知与困境
  • 【学习笔记】线性基
  • x86_64架构__rdtsc指令
  • KTT
  • AT_joisc2021_c フードコート (Foodcourt)
  • SPP question regarding Issues Due To Gaming Spoofers
  • 类型安全ORM的高并发场景解决方案 - 实践
  • L06_mybatis读取MySQL数据库(懵逼版)
  • 提供给第三方接口的验证方法
  • 【2025最新】6款免费DLL修复工具推荐:彻底解决“XXX.dll缺失”问题!
  • 2025 年注浆管生产厂家最新推荐排行榜:聚焦 0.3mm 精度与国企合作案例,助力基建企业精准挑选优质供应商
  • 2025 年试验箱厂家 TOP 企业品牌推荐排行榜,氙灯老化 / 紫外老化 / 冷热冲击 / 恒温恒湿 / 高低温 / 快速温变 / 盐水喷雾 / 高温老化 / 砂尘 / 淋雨试验箱公司推荐!
  • 系统修复
  • 什么是vibe ?
  • 2025年10月试验箱厂家最新推荐排行榜:氙灯老化试验箱,紫外老化试验箱,冷热冲击试验箱,恒温恒湿试验箱公司推荐!
  • AI时代我们需要更多开发者:Shalini Kurapati的技术洞察
  • 新一代虚拟助手AI技术挑战赛启动
  • CSS各种选择器
  • adobe illustrator中鼠标拖动移动幅度大
  • python的字符串方法示例
  • 经典视觉跟踪算法的MATLAB实现
  • aardio 调用vb函数
  • 2025年玻璃杯趋势:某某科技圆润咖啡杯引领健康饮水新潮流
  • 2025 年密封线优质厂家最新推荐榜:权威甄选螺纹、高强度等多类型密封线质量与技术双优企业液态/亚麻/防腐/耐高温密封线厂家推荐
  • 微算法科技(MLGO)发布隐私与能量感知联盟博弈算法,重塑边缘摄像头网络架构,推动物联网智能演进
  • adobe illustrator中设置键盘增量