回文自动机学习笔记

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了回文自动机学习笔记相关的知识,希望对你有一定的参考价值。

回文自动机学习笔记

这两天学习了回文自动机,于是在此总结一下,顺便复习一下所需要的预备知识。

1 【回文串基础】

1.1

回文串定义:长度为n,下标从0开始的字符串s是回文串,满足\(\forall i\in \mathbb{N},s[i]=s[n-1-i]\)

1.2

\(Manacher\)算法

这里用归纳法的方式简介地介绍这种算法:

\(h[i]\)表示以字符串第i个位置为中心的回文串长度。

假设已经计算出\(h[0],h[1]\dots h[i-1]\)的值。

可以由此计算出最大的回文串的右端点位置\(p\), 和右端点\(p\)对应回文中心\(pos\) 。(实际上可以直接继承)

如果\(p \ge i\), 则说明在\(pos\)为中心的回文串中有一个i的镜像位置。

  • 如果镜像位置的边界和\(pos\)对应回文串的边界不重叠,那么位置\(h[i]\)的长度不会超过镜像位置的长度。

  • 如果边界重叠,则\(h[0],h[1]\dots h[i]\)的pos和p可能被i刷新。

如果\(p < i\), 则说明在i可以刷新\(pos\)\(p\)

\(p\)\(pos\)不能被i刷新的时候,我们就可以从镜像位置继承\(h[i]\)的值

\(p\)\(pos\)可以被i刷新的时候,我们就可以同时推算出\(h[i]\)的值。

这里p每次被刷新,只会不断增大。所以可以证明算法的复杂度是和字符串长度呈线性关系的。

1.3

本质相同的回文子串 定义:如果两个子串长度相同,每个位置的元素相同,则是本质相同的回文串。

一个字符串里面,最多会有多少本质不同的回文子串?

在做\(Manacher\)的时候,只有拓展p的时候,可能产生新的回文串。(其余的回文子串一定在镜像位置出现过),所以答案和字符串长度线性相关。

2 【回文自动机】

2.0

自动机定义自行百度

2.1

结构:

自动机中每一个点表示一种回文串,且与自动机中的其他点表示的回文串本质不同。

一个回文串可以通过在两边添加各一个字符,变成另外一个回文串,则这两个回文串代表的点之间有一条有向边,方向从短的串指向长的串。

每一个点的fail指针指向它最长的回文后缀。

每一个点上记录回文串的长度,出现次数。

有向边组成了两颗树形图,一个表示奇数串,一个表示偶数串。

初始状态有两颗树的根节点分别表示一个长度-1的串和一个空串。

2.2

构造:

假设已经构造好了第1到i-1字符的的回文自动机,并记录右端点在i-1位置上的最长回文后缀节点,考虑添加第i个字符对它的影响。

由于我们已经构造好1到i-1所有的后缀,所有新产生的回文串一定是在位置i结尾的回文后缀。顺着i-1位置的回文后缀的fail指针往回跳,路途经过的回文后缀中满足左端点和右端点字符相同的回文串都是i结尾的回文串。

实际上我们只需要第一个左右端点相同的后缀打一个累加标记;如果这个位置是新的回文子串,则需要找到下一个回文后缀,并连上fail指针。而且根据回文串的对称性,不需要继续找下去了。

最后记得按拓扑序把标记依次下传就行了。

2.3

构造回文自动机的时间复杂度

  1. 根据1.3的结论,回文自动机总点数不超过原串字符串长度。
  2. 构造的时间复杂度,实际上和构造时寻找合适的回文后缀时“左右端点的移动次数+ 更新fail指针的移动次数”成正比。

根据第二点,我们只需要关心这两个东西就好了。

  1. 右端点移动次数:显然右端点单调向右,移动次数N。
  2. 右端点每移动一次,左端点向左移动一次,所以左端点顶多向右移动2N次,向左移动N次。
  3. fail指针移动次数和第2点分析方式类似。

总而言之,时间复杂度O(n)

//博主老了,不想写了,就这样吧(生无可恋(灬°ω°灬)

2.4

模板

int s[N], ch[N][26], fail[N], len[N], cnt[N], last, ecnt, n;
void init() {
    fail[0] = 1; fail[1] = 1; 
    len[0] = 0;  len[1] = -1;
    ecnt = 1; last = 1;
}
void extend( int w ) {
    int p = last; s[++n] = w;
    while( s[n-1-len[p]] != w ) 
        p = fail[p];
    if(!ch[p][w]) {
        int u = ++ecnt, v = fail[p];
        while( s[n-1-len[v]] != w ) v = fail[v];
        len[u] = len[p]+2; fail[u] = ch[v][w]; ch[p][w] = u;
    }
    last = ch[p][w]; cnt[last]++;
}

void calc() {
    for( int i = ecnt; ~i; i -- ) cnt[fail[i]] += cnt[i];
}

3 参考资料

CSDN上的一篇比较详细的博客

跟黄学长的版对拍了一下下

还有一篇,看过但是翻不到了。。。QAQ

4 吐槽

博主原来是想把这个东西讲得很细的,结果写到一半没了干劲(补番去了

如果想要细节去看其他博主的博客吧o(╥﹏╥)o

当然,如果文章里面有错误,但愿读者慷慨指出,多谢!

以上是关于回文自动机学习笔记的主要内容,如果未能解决你的问题,请参考以下文章

Byte of Python学习笔记——回文练习

学习笔记 链接

动态规划学习笔记

算法学习:回文自动机

回文树/回文自动机 Palindromic Tree 学习小记

Python 3学习笔记