总览:
真·整个人都自动机了
因为有奇偶回文串,所以有两个根,奇根偶根
每一个节点代表一个回文串的后一半
维护 len,代表回文串的长度
维护 fail,类似 ACM,代表最长回文后缀
注意初始化
模板:
const int A=5e5+5;
char s[A];
int len;
int tot=1,las=0;//总结点数,末字符所在回文串
struct PAM{
int ch[26];
int fail,len;
}tr[A];
inline int find(int x,int w){
while(s[w-tr[x].len-1]!=s[w]) x=tr[x].fail;
return x;
}//找可拓展的最长回文后缀
inline void insert(int x,int w){
int p=find(las,w);
if(!tr[p].ch[x]){
tr[++tot].len=tr[p].len+2;//前后各加一个字符
tr[tot].fail=tr[find(tr[p].fail,w)].ch[x];
tr[p].ch[x]=tot;
}
las=tr[p].ch[x];
return;
}
//初始化
tr[0].len=0,tr[1].len=-1;
tr[0].fail=1,tr[1].fail=0;
T1 P5496 【模板】回文自动机(PAM)
思路:
num 维护在 fail 树上的深度
代码:
#include<bits/stdc++.h>
using namespace std;
namespace IO{
char _buf_[1<<21],*_p1_=_buf_,*_p2_=_buf_;
#define ch() (_p1_==_p2_&&(_p2_=(_p1_=_buf_)+fread(_buf_,1,1<<21,stdin),_p1_==_p2_)?EOF:*_p1_++)
inline int in(){
int s=0,f=1;char x=ch();
while(x<'0'||x>'9'){
if(x=='-') f=-1;
x=ch();
}
while(x>='0'&&x<='9'){
s=(s*10)+(x&15);
x=ch();
}
return f==1?s:-s;
}
char _buf[1<<21];
int _p1=-1;
inline void flush(){
fwrite(_buf,1,_p1+1,stdout);
_p1=-1;
}
inline void pc(char x){
if(_p1==(1<<21)-1) flush();
_buf[++_p1]=x;
}
inline void out(int x){
char k[30];
int pos=0;
if(!x){
pc('0');
return;
}
if(x<0){
pc('-');
x=-x;
}
while(x){
k[++pos]=(x%10)|48;
x/=10;
}
for(int i=pos;i;i--) pc(k[i]);
return;
}
}
using namespace IO;
const int A=5e5+5;
char s[A];
int len;
int tot=1,las=0;
struct PAM{
int ch[26];
int fail,len,num;
}tr[A];
inline int find(int x,int w){
while(s[w-tr[x].len-1]!=s[w]) x=tr[x].fail;
return x;
}
inline void insert(int x,int w){
int p=find(las,w);
if(!tr[p].ch[x]){
tr[++tot].len=tr[p].len+2;
int tmp=find(tr[p].fail,w);
tr[tot].fail=tr[tmp].ch[x];
tr[tot].num=tr[tr[tot].fail].num+1;
tr[p].ch[x]=tot;
}
las=tr[p].ch[x];
return;
}
signed main(){
scanf("%s",s+1);
s[0]='&';
len=strlen(s+1);
tr[0].len=0,tr[1].len=-1;
tr[0].fail=1,tr[1].fail=0;
for(int i=1,k=0;i<=len;i++){
s[i]=(s[i]-97+k)%26+97;
insert(s[i]-'a',i);
out(k=tr[las].num),pc(' ');
}
flush();
return 0;
}
T2 P3649 [APIO2014]回文串
思路:
num 在 fail 树上的子树和维护该串的出现次数,最后要 树形dp
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
namespace IO{
char _buf_[1<<21],*_p1_=_buf_,*_p2_=_buf_;
#define ch() (_p1_==_p2_&&(_p2_=(_p1_=_buf_)+fread(_buf_,1,1<<21,stdin),_p1_==_p2_)?EOF:*_p1_++)
inline int in(){
int s=0,f=1;char x=ch();
while(x<'0'||x>'9'){
if(x=='-') f=-1;
x=ch();
}
while(x>='0'&&x<='9'){
s=(s*10)+(x&15);
x=ch();
}
return f==1?s:-s;
}
char _buf[1<<21];
int _p1=-1;
inline void flush(){
fwrite(_buf,1,_p1+1,stdout);
_p1=-1;
}
inline void pc(char x){
if(_p1==(1<<21)-1) flush();
_buf[++_p1]=x;
}
inline void out(int x){
char k[30];
int pos=0;
if(!x){
pc('0');
return;
}
if(x<0){
pc('-');
x=-x;
}
while(x){
k[++pos]=(x%10)|48;
x/=10;
}
for(int i=pos;i;i--) pc(k[i]);
return;
}
}
using namespace IO;
const int A=3e5+5;
char s[A];
int len;
int las=0,tot=1;
struct PAM{
int ch[26];
int fail,num,len;
}tr[A];
int ans=0;
inline int find(int x,int w){
while(s[w-tr[x].len-1]!=s[w]) x=tr[x].fail;
return x;
}
inline void insert(int x,int w){
int p=find(las,w);
if(!tr[p].ch[x]){
tr[++tot].len=tr[p].len+2;
tr[tot].fail=tr[find(tr[p].fail,w)].ch[x];
tr[p].ch[x]=tot;
}
las=tr[p].ch[x];
tr[las].num++;
return;
}
signed main(){
scanf("%s",s+1);
len=strlen(s+1);
s[0]='&';
tr[0].len=0,tr[1].len=-1;
tr[0].fail=1,tr[1].fail=0;
for(int i=1;i<=len;i++) insert(s[i]-'a',i);
for(int i=tot;i;i--){
ans=max(ans,tr[i].num*tr[i].len);
tr[tr[i].fail].num+=tr[i].num;
}
out(ans),pc('\n');
flush();
return 0;
}
T3 P4287 [SHOI2011]双倍回文
思路:
询问有多少个节点到根的路径上,存在一个节点代表的回文串长度为该节点的一半,哈希维护
代码:
#include<bits/stdc++.h>
#include<tr1/unordered_map>
using namespace std;
namespace IO{
char _buf_[1<<21],*_p1_=_buf_,*_p2_=_buf_;
#define ch() (_p1_==_p2_&&(_p2_=(_p1_=_buf_)+fread(_buf_,1,1<<21,stdin),_p1_==_p2_)?EOF:*_p1_++)
inline int in(){
int s=0,f=1;char x=ch();
while(x<'0'||x>'9'){
if(x=='-') f=-1;
x=ch();
}
while(x>='0'&&x<='9'){
s=(s*10)+(x&15);
x=ch();
}
return f==1?s:-s;
}
char _buf[1<<21];
int _p1=-1;
inline void flush(){
fwrite(_buf,1,_p1+1,stdout);
_p1=-1;
}
inline void pc(char x){
if(_p1==(1<<21)-1) flush();
_buf[++_p1]=x;
}
inline void out(int x){
char k[30];
int pos=0;
if(!x){
pc('0');
return;
}
if(x<0){
pc('-');
x=-x;
}
while(x){
k[++pos]=(x%10)|48;
x/=10;
}
for(int i=pos;i;i--) pc(k[i]);
return;
}
}
using namespace IO;
const int A=5e5+5;
int n;
char s[A];
int las=0,tot=1;
struct PAM{
int ch[26];
int fail,len;
}tr[A];
int ans=0;
inline int find(int x,int w){
while(s[w-tr[x].len-1]!=s[w]) x=tr[x].fail;
return x;
}
inline void insert(int x,int w){
int p=find(las,w);
if(!tr[p].ch[x]){
tr[++tot].len=tr[p].len+2;
tr[tot].fail=tr[find(tr[p].fail,w)].ch[x];
tr[p].ch[x]=tot;
}
las=tr[p].ch[x];
return;
}
int head[A],tot_road;
struct Road{
int nex,to;
}road[2*A];
inline void edge(int u,int v){
road[++tot_road]={head[u],v},head[u]=tot_road;
}
tr1::unordered_map <int,int> t;
inline void DFS(int x){
if(tr[x].len%4==0&&t[tr[x].len>>1]!=0) ans=max(ans,tr[x].len);
for(int y=head[x];y;y=road[y].nex){
int z=road[y].to;
t[tr[x].len]++;
DFS(z);
t[tr[x].len]--;
}
return;
}
signed main(){
scanf("%d%s",&n,s+1);
tr[0].len=0,tr[1].len=-1;
tr[0].fail=1,tr[1].fail=0;
for(int i=1;i<=n;i++) insert(s[i]-'a',i);
for(int i=tot;i;i--) edge(tr[i].fail,i);
DFS(0);
out(ans);
flush();
return 0;
}

本文深入解析回文自动机(PAM)的构建与应用,通过模板代码阐释如何维护回文串的长度、最长回文后缀及节点间的关系,探讨了在不同场景下如树形DP、哈希维护等高级技巧。

被折叠的 条评论
为什么被折叠?



