字符串笔记
匹配系列
kmp
kmp 的 fail 指针指向的是当前点最长的能作为原串的前缀的后缀在原串前缀上的位置。
i - fail[i] 就是前缀 i 的最短循环节,也称为 border。
一个例题:CF1286E。
AC 自动机
AC 自动机的本题是一棵 trie 树。
AC 自动机的 fail 指针,指向的是当前点代表的串的最长的、作为某个字符串的前缀出现的后缀所代表的节点。
设一个串 s 要匹配 AC 自动机上所有的串,那么一个 AC 自动机上的串出现,当且仅当这个串在 AC 自动机上的节点的后继中有节点被 s 访问过。
一般可以快速处理 一个串 对 多个串 的贡献。
后缀系列
后缀自动机
1、parent_tree
对于某一个子串,在某一些位置(为结尾)出现过,出现过的所有位置集合叫做endpos。
parent_tree是这样一棵树:一个节点代表一个endpos集合,节点上的字符串的endpos全部相等且恰为该节点代表的endpos集合;
每一个节点的endpos集合都是父亲节点的子集。
可以发现一个性质:设每一个点上的子串的最长串长为maxlen,最短为minlen,则父亲的maxlen+1必然等于儿子的minlen,而且minlen~maxlen必然每一个len都对应一个子串。
可以证明parent_tree的节点个数为O(n)。
后缀自动机的节点就是parent_tree的节点。
2、后缀自动机
自动机本身是个DAG。
通过DAG上从初始点出发的每一条路径,都能组成一个子串,且所有子串都能被表示出来。
维护下列东西:
fa:parent_tree上的fa。
len:每个节点的maxlen(如上所阐述)(minlen可以通过fa的maxlen来得到)
ch:儿子。DAG上的边指向的点。一个点的ch数量就是字符集,每一个ch代表了:在上一个点的子串后接上一个字符获得的子串所属的点。
每次加入一个字符c,创立一个新节点:
首先,对于上一次加入的字符的初始点,若它和它的祖先们(parent_tree上的)都没有儿子c,那就把儿子c连向自己。
然后找到第一个已经有了儿子c的点x。
考虑这个点的儿子c,称为ch[x][c]:如果这个len[x]+1=ch[x][c],说明ch[x][c]里面存的东西正好是符合parent_tree定义的,把新节点的fa连向它;
否则,将x点拆成两个点u、v:len[u]+1=ch[x][c],len[v]=len[x];然后,v和新节点的fa都是u。u的fa就是x的fa。把连向x的ch中长度满足条件的连向u,长度不满足条件的连向v。u和v的ch和x是一样的。
这样就维护好了,可以证明边数也是O(n)的。
3、经典操作
parent_tree从下到上更新:
按照maxlen来对节点基数排序。
求两个串的最长公共子串:
在自动机上面跑(往下走ch、往上跳fa)就好了。注意nowlen的更新。
4、例题:
XSY3930 最长公共子串对