【字符串】回文树&&回文自动机PAM
回文树&&回文自动机PAM
学习资料:hyfhaha-PAM学习小结
模板
回文树模板:
\(Fail\) 指针:当前节点的最长回文后缀。
题面:给你一个由小写拉丁字母组成的字符串 \(s\)。我们定义 \(s\) 的一个子串的存在值为这个子串在 \(s\) 中出现的次数乘以这个子串的长度。
对于给你的这个字符串 \(s\),求所有回文子串中的最大存在值。
char ss[maxn];
int last,tot,n;
int nex[maxn][27],len[maxn],fail[maxn],s[maxn],cnt[maxn];
/*
n 是字符串的长度
last 是当前节点的上一节点 记录信息的节点编号从1开始
next[i][j] 是 i节点左右添加字符x后所形成的新的字符串的 节点标号
len[i] 是i节点所代表的字符串的长度
fail[i] 是节点i的最长回文后缀的 节点标号
s[i] 是字符串的第i个字符
cnt[i] 是i节点所代表的的回文串在整个字符串里出现的次数
*/
void init()
{
n=strlen(ss);
for(int i=1;i<=n;i++)s[i]=ss[i-1]-'a';
s[0]=-1;fail[0]=1;last=0;
len[0]=0;len[1]=-1;tot=1;
}
/*
零号节点的最长回文后缀是指向1号节点 而1号节点的长度是 len[1]=-1;
这个操作有利于:
当添加第一个字符时(节点2) 新的回文串长度 恰好等于:-1+2=1
*/
int getfail(int x,int id){
while(s[id-len[x]-1]!=s[id])x=fail[x];
return x;
}
/*
从x节点往下找 找回文后缀恰好回文后缀的前一个字符等于要添加的字符
使得能构成一个新的回文后缀
若找不到,则结果是回到1号节点 然后再添加新字符 则形成了一个长度为1的回文串
len[1]=-1有利于
在找不到的情况下 最后一定能找到1号节点满足:
(s[id-len[1]-1]=s[id-(-1)-1])==s[id]
*/
//int newnode(int x){len[++tot]=x;return tot;}
void PT()
{
int p,q=1;
init();
for(int i=1;i<=n;i++)
{
p=getfail(last,i);
//找到上一节点可以添加s[i]字符的回文后缀所在的节点标号 p
if(nex[p][s[i]]==0)
{
// printf("%d %d ",i,q);
// q=newnode(len[p]+2);
len[q=++tot]=len[p]+2;//新字符串为p串两边添加s[i]
fail[q]=nex[getfail(fail[p],i)][s[i]];
//q的最长回文后缀是 p的满足前一个字符是s[i]的回文后缀再加字符s[i]的回文串所在节点 没有则为节点0
nex[p][s[i]]=q;
// for(int j=i-len[q]+1;j<=i;j++)printf("%c",s[j]+'a');putchar(10);
}
last=nex[p][s[i]];//将当前节点更新到上一节点
cnt[nex[p][s[i]]]++;//这个回文串出现的记录+1
}
for(int i=tot;i>1;i--)cnt[fail[i]]+=cnt[i];
/*
i节点出现必是包含其回文后缀的出现
所以最后需要更新回文后缀出现的次数
因为之前更新的节点所代表的串 都不会是别的串的最长回文后缀
*/
}
void solve()
{
ll ans=0;
PT();
for(int i=1;i<=tot;i++)ans=max(ans,1ll*len[i]*cnt[i]);
printf("%lld\n",ans);
}
int main()
{
scanf("%s",ss);
solve();
}
回文自动机模板:
\(Trans\) 指针:小于等于当前节点长度一半的最长回文后缀。
#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long ll;
const int maxn=3e5+50;
char s[maxn];
int fail[maxn],len[maxn],nex[maxn][27],trans[maxn];
int tot,last;
int getfail(int x,int id){
while(id-len[x]-1<0||s[id-len[x]-1]!=s[id])x=fail[x];
return x;
}
int gettrans(int x,int id){
while(((len[x]+2)<<1)>len[tot]||s[id-len[x]-1]!=s[id])x=fail[x];
return x;
}
void insert(int u,int i){
int q,p=getfail(last,i);
if(!nex[p][u]){
len[q=++tot]=len[p]+2;
fail[q]=nex[getfail(fail[p],i)][s[i]];
nex[p][u]=q;
if(len[q]<=2)trans[q]=fail[q];
else{
int t=gettrans(trans[p],i);
trans[q]=nex[t][u];
}
}
last=nex[p][u];
}
例题
题意:求连续两个回文串的最长长度
解:分别建正序和逆序的两颗回文树
#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long ll;
const int maxn=3e5+50;
char ss[maxn];
struct PT{
int last,tot,n;
int nex[maxn][27],len[maxn],fail[maxn],s[maxn];
int l[maxn];
void init()
{
n=strlen(ss);
for(int i=1;i<=n;i++)s[i]=ss[i-1]-'a',l[i]=1;
s[0]=-1;fail[0]=1;last=0;
len[0]=0;len[1]=-1;tot=1;
}
int getfail(int x,int id){
while(s[id-len[x]-1]!=s[id])x=fail[x];
return x;
}
void solve()
{
int p,q=1;
init();
for(int i=1;i<=n;i++)
{
p=getfail(last,i);
if(nex[p][s[i]]==0)
{
len[q=++tot]=len[p]+2;
fail[q]=nex[getfail(fail[p],i)][s[i]];
nex[p][s[i]]=q;
}
last=nex[p][s[i]];
l[i]=len[last];
}
}
}pt1,pt2;
void solve()
{
int ans=0,len=strlen(ss);
pt1.solve();
for(int i=0;(i<<1)<len;i++)swap(ss[i],ss[len-i-1]);
pt2.solve();
for(int i=1;i<len;i++)ans=max(ans,pt1.l[i]+pt2.l[len-i]);
printf("%d\n",ans);
}
int main()
{
scanf("%s",ss);
solve();
}
题意:给定字符串 \(s\) ,对于 \(s\) 每个位置,青丘处以该位置结尾的回文子串个数。
该字符串加密,每个字符需要通过上一个字符的答案来解密: \(s[i+1]=(s[i]-97+lans)%26+97\)
解:一个回文串的答案等于其最长回文后缀的答案+1
#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long ll;
const int maxn=5e5+50;
char ss[maxn];
struct PT{
int last,tot,n;
int nex[maxn][27],len[maxn],fail[maxn],s[maxn];
int num[maxn];
void init()
{
n=strlen(ss);
for(int i=1;i<=n;i++)s[i]=ss[i-1]-'a';
s[0]=-1;fail[0]=1;last=0;
len[0]=0;len[1]=-1;tot=1;
}
int getfail(int x,int id){
while(s[id-len[x]-1]!=s[id])x=fail[x];
return x;
}
void solve()
{
int p,q=1;
init();
for(int i=1;i<=n;i++)
{
p=getfail(last,i);
if(nex[p][s[i]]==0)
{
len[q=++tot]=len[p]+2;
fail[q]=nex[getfail(fail[p],i)][s[i]];
nex[p][s[i]]=q;
}
last=nex[p][s[i]];
num[last]=num[fail[last]]+1;
printf("%d ",num[last]);
s[i+1]=(s[i+1]+num[last])%26;
}
}
}pt;
int main()
{
scanf("%s",ss);
pt.solve();
}
题意:求字符串 \(s\) 的最长子串 \(p\) 的长度, \(p\) 满足 \(|p|\) 是 4 的倍数,且 \(p\) 和 \(\frac{p}2\) 都是回文串
解: 利用 \(trans\) 指针即可。
#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long ll;
const int maxn=5e5+50;
char ss[maxn];
struct PT{
int last,tot,n;
int nex[maxn][27],len[maxn],fail[maxn],s[maxn],trans[maxn];
void init()
{
n=strlen(ss);
for(int i=1;i<=n;i++)s[i]=ss[i-1]-'a';
s[0]=-1;fail[0]=1;last=0;
len[0]=0;len[1]=-1;tot=1;
}
int getfail(int x,int id){
while(s[id-len[x]-1]!=s[id])x=fail[x];
return x;
}
int gettrans(int x,int id){
while(((len[x]+2)<<1)>len[tot]||s[id-len[x]-1]!=s[id])x=fail[x];
return x;
}
void solve()
{
int p,q=1;
init();
for(int i=1;i<=n;i++)
{
p=getfail(last,i);
if(nex[p][s[i]]==0)
{
len[q=++tot]=len[p]+2;
fail[q]=nex[getfail(fail[p],i)][s[i]];
nex[p][s[i]]=q;
if(len[q]<=2)trans[q]=fail[q];
else{
int t=gettrans(trans[p],i);
trans[q]=nex[t][s[i]];
}
}
last=nex[p][s[i]];
}
int ans=0;
for(int i=1;i<=tot;i++)
if(len[trans[i]]*2==len[i]&&len[i]%4==0)ans=max(ans,len[i]);
printf("%d\n",ans);
}
}pt;
int main()
{
int n;
scanf("%d",&n);
scanf("%s",ss);
pt.solve();
}