Palindromic Tree 回文自动机-回文树 解决回文串的神器

回文树,也叫回文自动机,是2014年夏天战斗民族发明的,其功能如下:

1、求前缀字符串中的本质不同的回文串种类

2、求每个本质不同回文串的个数

3、以下标i为结尾的回文串个数/种类

4、每个本质不同回文串包含的本质不同回文串种类

(本文参考自Palindromic Tree——回文树【处理一类回文串问题的强力工具】)


在回文树中,每一个节点代表一个本质不同的回文串,因为长度为n的字符串最多有本质不同的回文串n个,故回文树中至多有n节点,再加上两个初始节点,至多有n+2个节点。

(以下代码和理解也是学习的鸟神的Palindromic Tree——回文树【处理一类回文串问题的强力工具】,自己修改后的理解

首先我们定义一些变量。
1.len[i]表示编号为i的节点表示的回文串的长度
2.next[i][c]表示编号为i的节点表示的回文串在两边添加字符c以后变成的回文串的编号(和Trie类似)。
3.fail[i]表示节点i失配以后跳转不等于自身的节点i表示的回文串的最长后缀回文串(类似后缀自动机的parent指针)。
4.cnt[i]表示节点i表示的本质不同的串的个数(建树时求出的不是完全的,最后count()函数按照拓扑序跑一遍以后才是正确的)
5.num[i]表示以节点i表示的最长回文串的最右端点为回文串结尾的回文串个数,也可以用来在建树过程中统计下标j的为结尾的回文串个数。
6.last指向新添加一个字母后所形成的最长回文串表示的节点。
7.S[i]表示第i次添加的字符(一开始设S[0] = -1(可以是任意一个在串S中不会出现的字符))。
8.p表示添加的节点个数。
9.n表示添加的字符个数。

那么我们在建树过程中,每添加一个字符c,至多会增加一个新的种类的回文串(或是之前已经有的回文串),由于形成的最长的回文串一定是以字符c结尾,那么这个回文串必定是cTc形式,其中T为回文串。想到这里就可以使用last的fail指针可以递归找到一个最长的回文后缀且前一个字符为c,否则此回文串为c,可以看代码理解。

回文树最重要的是建树过程,在建树时num[last]即是以此回文串的结尾为尾端的回文串种类,也是以当前下标为结尾的回文串的个数。

最后延拓扑序(类似后缀自动机,不过这里的拓扑序就是是从p-1到0)更新cnt

下面给出代码:

const int maxn = 100005;// n(空间复杂度o(n*ALP)),实际开n即可
const int ALP = 26;

struct PAM{ // 每个节点代表一个回文串
    int next[maxn][ALP]; // next指针,参照Trie树
    int fail[maxn]; // fail失配后缀链接
    int cnt[maxn]; // 此回文串出现个数
    int num[maxn];
    int len[maxn]; // 回文串长度
    int s[maxn]; // 存放添加的字符
    int last; //指向上一个字符所在的节点,方便下一次add
    int n; // 已添加字符个数
    int p; // 节点个数

    int newnode(int w){ // 初始化节点,w=长度
        for(int i=0;i=0;i--)
            cnt[fail[i]] += cnt[i];
    }
}pam;

构造回文树需要的空间复杂度为O(N *字符集大小) ,时间复杂度为O(N*log(字符集大小)),这个时间复杂度比较神奇。如果空间需求太大,可以改成邻接表的形式存储,不过相应的要牺牲一些时间。

邻接表用vector实现,代码可以参考TsinsenA1393. Palisection的代码。


给出几道练习题,代码也会在下一篇文章中给出。

1.ural1960. Palindromes and Super Abilities
2.TsinsenA1280. 最长双回文串
3.TsinsenA1255. 拉拉队排练
4.TsinsenA1393. Palisection
5.2014-2015 ACM-ICPC, Asia Xian Regional Contest G The Problem to Slow Down You
6.bzoj 3676


你可能感兴趣的:(字符串)