PAM总结

回文自动机高度压缩了字符串中所有回文子串的信息,维护了原串中所有本质不同的回文子串。

PAM 的结构可以看作两棵树,但实际上是一棵。有两个根,分别是奇根 \(odd\) 和偶根 \(even\)\(odd\) 上连着所有长度为奇数的回文子串,自身长度置为 \(-1\)\(even\) 上连着所有长度为偶数的回文子串,自身长度置为 \(0\)

来说转移边,\(u\) 的一条 \(c\) 的转移边指向 \(v\),表示 \(s_v\)\(s_u\) 的前后都加上 \(c\) 所形成的回文串。特别的,\(odd\) 连出来的边指向长度为 \(1\) 的回文子串。

来说 \(fail\) 边,对每个结点 \(u\)\(fail_u\) 指向它的最长回文后缀所代表的结点。显然 \(fail\) 边形成了类似树的结构。\(even\)\(fail\) 边指向 \(odd\)\(odd\)\(fail\) 边指向自身。

构建

构建 PAM 的复杂度是 \(O(n)\) 的。

有引理:长度为 \(|s|\) 的字符串 \(s\) 的本质不同回文子串至多有 \(|s|\) 个。

证明一下,上数归。\(|s|=1\) 时显然成立。设已有的字符串 \(s=t+c\),假设该结论对 \(t\) 成立。设以 \(c\) 为右端点的回文子串的左端点分别为 \(l_1,l_2\dots l_k\),设 \(l_i<l_{i+1}\)。于是在回文子串 \(s_{l_1\dots |s|}\) 中用一下回文的性质,可以发现以 \(l_2,l_3\dots l_k\) 为左端点且以 \(c\) 为右端点的回文子串已经在 \(t\) 中出现过,于是 \(s\) 中至多比 \(t\) 中新增一个本质不同回文子串。于是得证。

所以 PAM 的结点数是线性的,转移是唯一的,于是 \(O(n)\)

对每个结点要维护该结点对应回文子串长度 \(len\)\(fail\) 边。

考虑增量法构建 PAM。新增一个字符 \(c\),由以上引理,每次插入一个字符最多新增一个结点,即当前字符串的最长回文后缀。记 \(last\) 为上次插入后最长回文后缀对应的点。设新增的结点为 \(u\),当前下标为 \(i\),那么 \(u\)\(x\) 转移过来,当且仅当 \(x\)\(last\) 的一个回文后缀,且 \(s_{i-len_x+1}=c\)\(x\) 可以通过从 \(last\) 开始不断跳 \(fail\) 边来找到。这时要判断一下 \(x\)\(c\) 转移边指向的点是否已经存在,如果还没有,那就新建结点,\(len_u=len_x+2\)

考虑求出 \(u\)\(fail\)。可以发现 \(u\)\(fail\) 一定是 \(x\) 的一个回文后缀在前后加上字符 \(c\)。如果 \(x\) 为奇根,那么当前字符串的最长回文后缀为 \(c\),那么 \(u\)\(fail\) 直接连到 \(0\)。否则就从 \(fail_x\) 开始跳,设现在跳到的点为 \(y\),直到 \(s_{i-len_y+1}=c\),那么此时的 \(y\)\(c\) 转移边指向的结点就是 \(u\)\(fail\)。显然这个点是已经建立过的,不必新建结点,因为由于 \(x\) 的回文性质且 \(x\) 的前后都为 \(c\),那么 \(y\)\(x\) 中对应的与之回文的前缀一定也满足前后都为 \(c\),而这个点在 PAM 中已经有了。

证明复杂度,除了跳 \(fail\) 的过程,其他显然是 \(O(n)\) 的。于是来分析跳 \(fail\) 的过程,上 \(fail\) 树。这里将 \(odd\)\(even\) 视作深度为 \(0\)。每次新加一个结点,在 \(fail\) 树上的动作表现为:先不断跳父亲,然后连边新建结点。跳一次父亲使深度 \(-1\),新建一个结点使深度 \(+1\),然后使用势能分析(?)或者人类智慧(?)可以知道跳 \(fail\) 的总次数不会超过 \(2n\),所以是 \(O(n)\) 的。

或许还有更好理解而不严谨的解释。每次加入一个字符后,最长回文后缀的长度至多 \(+2\),每次跳 \(fail\) 至少使最长回文后缀的长度 \(-1\),总共加入 \(n\) 次字符,所以至多跳 \(2n\)\(fail\)

应用

本质不同回文子串个数

就是 PAM 的状态数 \(-2\)(减去 \(odd\)\(even\))。

求每个回文子串在原串中的出现次数

考虑增量法构建的过程。在加入一个字符后,当前字符串的所有回文后缀的出现次数都要 \(+1\),于是直接在最长回文后缀对应结点上打上 \(tag\),最后按拓扑序推平就好。而 PAM 的构建中本就有拓扑序(一个点的 \(fail\) 的编号肯定比它自己小),于是在 PAM 的结点中按编号从大到小扫一遍推平就行。

求前一半是偶回文串,后一半也是偶回文串的回文子串

显然这种回文子串的长度为 \(4\) 的倍数,但这个性质没什么用。

定义 \(trans\) 指针,指向长度不超过当前节点长度的一半的结点。那么此题就是要找 \(trans\) 指向的结点的长度恰好为当前节点长度的一半的结点。维护 \(trans\) 的手法与 \(fail\) 类似,若 \(fail\le 2\),那么 \(trans=fail\)。否则设当前节点为 \(u\)\(x\) 经过 \(c\) 转移边指向 \(u\),那么从 \(trans_x\) 开始跳 \(fail\) 找到符合的结点即可。

其他待补

posted @ 2025-04-18 17:08  RandomShuffle  阅读(19)  评论(0)    收藏  举报