后缀数组

基础结构

后缀数组主要是有两部分共同构成,分别是 \(tr[][]\) 边和 \(link[]\) 边,

后缀数组中,一个状态也代表一个字符串 , 注意了 ,因为后缀数组是个 DAG , 所以对于 \(x\) 点,认定其状态是从起点走到他自己的最长路径 (最长的字符串) ,而其他路径都是 \(x\) 字符串的后缀。

\(tr[x][i]\) 和字典树含义一样,指的是 \(x\) 字符串接上 \(i\) 就是 \(tr[x][i]\) 字符串,同时顺着 \(tr\) 走可以走完一个字符串里所有的子串。

\(link[x]\) 则代表当前后缀数组中 , \(x\) 字符串与其他存在于后缀数组的字符串的最长公共后缀 (也是一个字符串)。

此外,还会记录一个 \(len[x]\) ,表示 \(x\) 状态的字符串的长度。

构建方法

对一个字符串构建后缀数组,总体上是将其所有的前缀从短到长依次都加入后缀数组中。

而一步一步来看,实际上第i个前缀我们都只是把 \(s[i]\) 这个字符加入。

我们假设已经正在加入 \(c\) 字符,上一个加入的点是 \(last\) ,这一次加入的点是 \(nxt\) ,很明显的是 \(tr[last][c]=nxt\)

那么主要是找到 \(link[nxt]\) 了,我们设 \(p=last\) ,然后让 \(p\) 沿着 \(link[p]\) 往回跳:

  1. 如果 \(tr[p][c]\) 不存在,那么 \(tr[p][c]\) 可以直接连到 \(nxt\)

  2. 如果 \(tr[p][c]\) 存在,且 \(len[tr[p][c]]=len[p]+1\) ,则 \(link[nxt]=tr[p][c]\) 。终止操作。

  3. 如果 \(tr[p][c]\) 存在,但 \(len[tr[p][c]]>len[p]+1\) ,则进行下列操作:

\(q = tr[p][c]\) ,新建一个 \(clone\) 点,完全将 \(q\) 点的 \(link\)\(tr\) 复制到 \(clone\) , 随后将 \(link[q]\) 指向 \(clone\) 并且顺着 \(p\) 回跳 , 把 \(tr[p][c]=q\) 的 改为 \(tr[p][c]=clone\) 。终止操作。

可以发现,加入 \(s[i]\) 后,相当于加入了以 \(i\) 为结尾的前缀,随后 \(link\) 边会更新改变, \(link\) 边会形成一棵树,两点 LCA 为其最长公共后缀。

未完待续

posted @ 2020-01-08 16:24  redegg  阅读(238)  评论(0编辑  收藏  举报