Audrey_Hall
2022-03-23 18:39:20
另一种阅读体验
字符串哈希
Trie 树
在计算机科学中,trie 树,又称前缀树或字典树,是一种有序树,用于保存关联数组,其中的键通常是字符串。
与二叉查找树不同,键不是直接保存在节点中,而是由节点在树中的位置决定。
一个节点的所有子孙都有相同的前缀,也就是这个节点对应的字符串,而根节点对应空字符串。
一般情况下,不是所有的节点都有对应的值,只有叶子节点和部分内部节点所对应的键才有相关的值。
在 Trie 上,两个字符串的最长公共前缀(LCP)就是两个点的最近公共祖先(LCA)所对应的点。
有些时候,我们也可以将数字看作一个二进制数,或者输一个由
KMP 算法
KMP 算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。
前置知识
border 指所有那些既是前缀又是后缀,但不是本身的子串。
border 的 border 仍是 border。
一个串的所有 border 互为 border。
若 Lborder 为一个串的最长 border,那么一个串的 Lborder,Lborder 的 Lborder
所以实际上每个前缀都指向自己的 Lborder(没有则指向超级根),可以形成一个树形结构,每个点到根的路径就是所有 border。
考虑把 fail
指针)。
先将
当遇到无法匹配的情况时,希望找到
注意这里实际上和
换句话说,我们要对每个点找到其最长后缀也是某个 fail
)。
具体的找法用一个 BFS 就能实现。
除了一些特殊情况,一个点的 fail
,就是其 fail
的 fail
的对应子节点(如果有的话),没有的话就继续跳 fail
。
可以证明这样做的复杂度是正确的。
匹配 fail
指针向上跳直到可以继续匹配为止。
这样复杂度显然是线性的。
注意问题
一个点对应的字符串出现的次数需要统计子树和,比如 abcd 的对应节点出现一次,那么 bcd,cd,d 都要出现一次。
直接如此实现会有常数上的劣势,一个显著的改进方法是:若该点有
只需要在求 fail
的时候,对于第二种情况(假设当前是
这样能显著优化常数,而且在做另一些问题时能简化问题。
给 n 个串然后两两询问的题有些时候的处理方法
考虑选一个适当大小的数字
若询问两个长度均不超过
若询问中有至少一个串长度超过
因此总复杂度
取
不过大多数题
具体取多少还可以适当调参来加速。(因为两部分算法有常数差异)
后缀数组 SA
SA 能在
一个简单的推论是,两个后缀的 LCP 就是后缀数组对应位次间所有相邻字符串 LCP 的最小值,这样可以 RMQ(区间最值查询)。
可以利用 SA 求一个字符串有多少本质不同的子串(即长的不一样),或者求一个字符串的第
这些问题本质相同:
考虑如何按从小到大的顺序得到
由于子串就是后缀的前缀,所有我们只需要考察所有后缀的前缀即可。
从小到大考虑
那么这两个问题解决了。
第二个问题通过二分可以
同理也可以求一个子串的位次,方法是先找到所在后缀,对应到 SA 上,考虑二分出一个最小的以这个子串为前缀的后缀(即两个后缀的 LCP 长度大于等于子串长度),然后求一下现在这个后缀和前一个后缀的 LCP 即可算出该子串的位次。
因此可以用 SA 实现子串和位次的转化。
同时参照上面的方法也可以求出一个子串在哪些位置出现过。
后缀树
后缀树是一种数据结构,是前缀树里的一个特殊类型,能快速解决很多关于字符串的问题。
一个 string S
的后缀树是一个边被标记为字符串的树。
因此每一个
SAM(后缀自动机)
SAM 就是将 DFA 与后缀结合,将重复的后缀压缩成只有一个,这样省下了空间。
后缀自动机厉害的地方就在于空间时间都是线性复杂度的,十分优秀。
SAM 的每个状态 substrings(st)
,并且对于两个不同状态
每个子串都恰好被一个状态包含,所以我们只要构造出来 st
求
SA-IS
SA−IS 排序是基于诱导排序这种思想,将问题规模缩小,解决更小的问题,快速解决原问题的算法。
Manacher 算法
Manacher 算法,又叫“马拉车”算法,可以在时间复杂度为
在进行 Manacher 算法时,字符串都会进行一个字符处理,比如输入的字符为 acbbcbds,用“#”字符处理之后的新字符串就是 #a#c#b#b#c#b#d#s#。
回文自动机
同其他自动机一样,回文自动机是个 DAG(有向无环图),它用相当少(
和别的自动机不太一样,回文自动机是有两棵树的森林:其中一棵是长度为偶数的回文串集合,另一棵是长度为奇数的回文串集合,这两棵树的根节点分别表示长度为
一个回文自动机包含不超过 fail
边连向这个点的 fail
自动机中每条有向边都有一个字符类型的权值,起点的串左右分别加上这个字符得到的就是终点的串。
举个例子:设一条边权为
特别的,如果
当你插入一个字符的时候,插入的点代表的就是这个字符匹配的最长回文串,也就是说从根节点往下顺着边走,记着一个 str
一开始为空,一边走一边不停地往 str
左右两边添加新的字符,走到一个点,这个点代表的回文串就是 str
。
每个点都有个 fail
边,这条边指向这个点所代表的回文串的最长回文后缀所在的那个点(最长回文后缀:串中满足回文的最长的后缀,这个串自己不算)。
如果没有,则指向
特别的,fail
节点就是那个长度为