【字符串】回文树&&回文自动机PAM

回文树&&回文自动机PAM

学习资料:hyfhaha-PAM学习小结

OI Wiki 回文树

模板

回文树模板:

\(Fail\) 指针:当前节点的最长回文后缀。

例题:luoguP3649回文串

题面:给你一个由小写拉丁字母组成的字符串 \(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];
}


例题

1,luoguP4555最长双回文串

题意:求连续两个回文串的最长长度

解:分别建正序和逆序的两颗回文树

#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();
}

2,P5496【模板】回文自动机

题意:给定字符串 \(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();
}

3,P4287双倍回文

题意:求字符串 \(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();
}
posted @ 2020-10-11 11:06  草丛怪  阅读(169)  评论(0编辑  收藏  举报