• 【字符串】回文树&&回文自动机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
    ",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
    ",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
    ",ans);
        }
    }pt;
    int main()
    {
        int n;
        scanf("%d",&n);
        scanf("%s",ss);
        pt.solve();
    }
    
  • 相关阅读:
    SDN第三次作业
    SDN第二次上机作业
    SDN第二次作业
    JAVA小记
    算法笔记
    排序
    SDN期末作业
    SDN第五次上机作业
    SDN第四次上机作业
    SDN第四次作业
  • 原文地址:https://www.cnblogs.com/kkkek/p/13796604.html
Copyright © 2020-2023  润新知