后缀数组
基础结构
后缀数组主要是有两部分共同构成,分别是 \(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]\) 往回跳:
-
如果 \(tr[p][c]\) 不存在,那么 \(tr[p][c]\) 可以直接连到 \(nxt\) 。
-
如果 \(tr[p][c]\) 存在,且 \(len[tr[p][c]]=len[p]+1\) ,则 \(link[nxt]=tr[p][c]\) 。终止操作。
-
如果 \(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 为其最长公共后缀。