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\) 找到符合的结点即可。

浙公网安备 33010602011771号