20200901 专题:回文自动机

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

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

总览:

真·整个人都自动机了
因为有奇偶回文串,所以有两个根,奇根偶根
每一个节点代表一个回文串的后一半
维护 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]回文串

思路:
numfail 树上的子树和维护该串的出现次数,最后要 树形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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值