后缀数组虽然强大,后缀自动机也不可忽视,而且后缀自动机有它独特的特点,对于某些题目可能更得心应手;
什么是后缀自动机,他处于什么目的的数据结构,他为什么能省时?
假如我们把一个长度为len的字符串s的所有后缀存放在一棵根节点为root的树中,势必会有0(len * len)个节点,这对于一个len比较大的字符串而言不可想象,
但字符本身就只有26个,是不是很多节点都可以共用呢?
基于这一考虑就有了后缀自动机的构造办法;
假如现有s的自动机a,在s后面加上x后,如何在a的基础上得到sx的自动机a‘;
1,首先,一个自动机他有若干个尾节点(尾节点就相当于上面的树的叶子节点,当s追加字符x时,所有的尾节点都加上x就能保证得到sx的自动机);
2,对于一个尾节点,他如果已经有了x后续字符,那么我们就要考虑两种情况了;
假设尾节点为a,尾节点的字符x后续节点为b,我们设len【a】为节点a的最长s子串长度;
(1)len【a】 + 1 = len[b]; 很容易想到b节点的所有子串都是从a节点过去的,这种情况下只要把b节点设置成下一次的尾节点就可以了;
(2)len【a】 + 1 != len【b】,这种情况下,就不能直接把b节点设置成下次的尾节点了,因为b节点可能是其他节点的后续节点,那么当b
成了尾节点,在b上加节点了之后可能就增加了自动机的不可靠信息;
这个时候我们可以另外再开一个节点q,复制所有b节点的信息到q上,让a的后续x节点指向q,让len【q】 = len【a】 + 1,就能构造满足(1)的条件
需要注意的是,每个节点中的pre指向的是下一个尾节点,因为尾节点不止一个;
算法模板:
int add(int x)
{
int p = num ++;
init(p);
node[p].step = node[live].step + 1;
for(; live != -1 && node[live].son[x] == -1; live = node[live].pre) //尾节点没有x后续节点的时候,直接指向新建的节点
node[live].son[x] = p;
if(live == -1)
{
node[p].pre = 0; //0节点总是尾节点
}
else
{
int q = node[live].son[x];
if(node[live].step + 1 == node[q].step) //尾节点有x后续节点,满足(1)条件
{
node[p].pre = q;
}
else
{
int nq = num ++; //尾节点有后续节点,不满足(1)条件,构造满足(1)的新节点
copySon(nq, q);
node[nq].step = node[live].step + 1;
node[nq].pre = node[q].pre;
node[q].pre = node[p].pre = nq; //新建的节点时下一次的尾节点
for(; live != -1 && node[live].son[x] == q; live = node[live].pre) //让所有之前指向q的现在指向新建的节点
node[live].son[x] = nq;
} //值得注意的是为什么只要遇到尾节点y存在x后续节点的时候,就不用再遍历更前面的尾节点了,
//因为这个尾节点y有x后续节点,可以想到y的pre节点也一定有了x后续节点,因为算法是从前之后的,想想前面在y加x后续节点时所做 //的事情
}
live = p;
return 0;
}