回文自动机

特别说明:增量算法是极其重要的算法证明和构造,尤其是在复杂状态转移时使用很多,z算法求本质不同回文时也用到了

  • 特别鸣谢:翁文涛 – 2017 国家候选队论文《回文树及其应用 》
  • 特别鸣谢:bzt的题解

一,定义

  • PAM 是一个接受且仅接受某个字符串的所有回文子串的中心及右半部分的 DFA。
  • 保证回文串为奇回文串,如果不是需要构造,如此PAM就是一颗自动机而非分裂的子树
  • 这是一颗成品 PAM ~ 看起来不那么离散嘛

在这里插入图片描述

二,状态概述

  • PAM 的每个状态都表示一个回文子串,其中包含两个特殊状态, 长度 为 0 和 -1,它们分别作为偶回文子串和奇回文子串两棵树的根。
    PAM 的转移表示在串的两侧各加上同一个字符,因此长度也会加二。PAM 显然是分别以 0 和 -1 为根的两棵树,因为每个状态由唯一的状态转移而来(删掉两端的字符)。
  • fail边:表示非平凡后缀中在自动机里的最长状态(也就是最长回文非平凡后缀)

三,引理

在一个字符串后添加一个字符,至多增加一个之前没有出现过的回文子串,且该回文子串必定是原串的一个回文非平凡后缀两侧加上新添加的这个字符。

  • 折半感性理解即可

四,具体理论(增量算法)

  • O ( 1 ) O(1) O(1)的复杂度每次添加一个新的字符并维护PAM
  • fail指针树上深度转化每次操作不超过 1 1 1 次,复杂度线性
  • 转移条件: s t r [ p o s − l e n − 1 ] = = s [ p o s ] str[pos-len-1]==s[pos] str[poslen1]==s[pos],fail指针在new——point时,若是节点性质为 − 1 -1 1,fail指向 0 0 0

具体如何维护 f a i l fail fail呢?

1,我们只要找到了当前节点的最长回文后缀然后记录一下

2,字符要接在之前的串的后面,记录一下 l a s t last last 表示上一个串的节点

3,注意特殊处理两个根节点, 0 0 0 代表长度为偶数的后缀的根, 1 1 1 代表长度为 1 1 1的后缀的根,我们令 f a i l [ 0 ] fail[0] fail[0] 指向 1 1 1 l e n [ 1 ] = − 1 len[1]=-1 len[1]=1,然后令 s [ 0 ] = − 1 s[0]=-1 s[0]=1(或任何一个不在原串中出现的字符)( l e n len len代表这个节点的串长)

就比如说如果跳的时候一直找不到回文怎么办?这个时候这个节点就单独形成一个回文串,那么我们在判断 s [ i − l e n [ x ] − 1 ] = = s [ i ] s[i-len[x]-1]==s[i] s[ilen[x]1]==s[i]的时候,因为 l e n [ 1 ] = − 1 len[1]=-1 len[1]=1,所以必定会停止,那么就不用担心会无限跳下去了

struct PAM
{
    int len, fail, ch[26];
}pam[N];
char s[N];
int n, p = 2, tot = 2, pos;
int main()
{
    scanf("%s", s + 1);
    n = strlen(s + 1);
    pam[1].len = -1;
    pam[2].fail = 1;
    for (pos = 1; pos <= n; ++pos) extend();
    return 0;
}
void extend()
{
    int x = s[pos] - 'a';
    while (s[pos - pam[p].len - 1] != s[pos]) p = pam[p].fail;
    if (pam[p].ch[x]) p = pam[p].ch[x];
    else
    {
        int np = ++tot;
        pam[p].ch[x] = np;
        pam[np].len = pam[p].len + 2;
        if (p == 1) pam[np].fail = 2;
        else
        {
            for (p = pam[p].fail; s[pos - pam[p].len - 1] != s[pos]; p = pam[p].fail);
            pam[np].fail = pam[p].ch[x];
        }
        p = np;
    }
}

例题

1,P3649 [APIO2014] 回文串

  • 这是一道亚太赛区的题!
  • 给你一个由小写拉丁字母组成的字符串 s s s。我们定义 s s s 的一个子串的存在值为这个子串在 s s s 中出现的次数乘以这个子串的长度,对于这个字符串 s s s,求所有回文子串中的最大存在值
  • 分析:涉及回文,后缀数组和SAM 是解决不了,后缀平衡树?不不不,还得是PAM
  • 嵌入计数器
struct PAM
{
    int len, fail, ch[26];
}pam[N];
char s[N];
int n, p = 2, tot = 2, pos;
int main()
{
    scanf("%s", s + 1);
    n = strlen(s + 1);
    pam[1].len = -1;
    pam[2].fail = 1;
    for (pos = 1; pos <= n; ++pos) extend();
    return 0;
}
void extend()
{
    int x = s[pos] - 'a';
    while (s[pos - pam[p].len - 1] != s[pos]) p = pam[p].fail;
    if (pam[p].ch[x]) p = pam[p].ch[x];
    else
    {
        int np = ++tot;
        pam[p].ch[x] = np;
        pam[np].len = pam[p].len + 2;
        if (p == 1) pam[np].fail = 2;
        else
        {
            for (p = pam[p].fail; s[pos - pam[p].len - 1] != s[pos]; p = pam[p].fail);
            pam[np].fail = pam[p].ch[x];
        }
        p = np;
    }
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流苏贺风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值