[XJ3]字符串问题

字符串哈希

字符串Hash是一种将任意长度的字符串映射成一个非负整数的算法。
将字符串视作 \(𝑃\) 进制数,对于字符集中的每一个字符,分配一个大于 \(0\) 的数值。

字符集:指可能出现在字符串中所有的字符组成的集合。例如小写字符串的字符集为 {𝑎, 𝑏, 𝑐, … , 𝑧}。

我们需要对这个 \(P\) 进制数取模,假设模数为 \(𝑀\)
在实践中,可以使用 \(unsigned long long int\) 类型存储 Hash值,在计算过程中不处理算数溢出问题。

unsigned long long int 类型在溢出时会自动对 264 取模,即取 𝑀 = 264。

记字符串 \(𝑆\) 的Hash值为 \(𝐻(𝑆)\) ,在字符串 \(𝑆\) 后增加一个字符 \(𝑐\) ,则有 \(𝐻(𝑆+c)=(𝐻(𝑆)×𝑃+𝑉(𝑐)) mod 𝑀\)。其中 \(𝑉(𝑐)\) 代表给字符 \(𝑐\) 赋予的值。(可加性)

记字符串 \(𝑆 + 𝑇\) 的Hash值为 \(𝐻(𝑆 + 𝑇)\),则有 \(𝐻(𝑇) = (H(S+T)-H(S)xP^ {\left| T \right|} )mod\ M\) 其中 𝑇 代表字符串 𝑇 的长度。(可减性)
为保证时间复杂度,需要预处理 𝑃 的若干次方。

可以使用前缀和来处理字符串Hash。

例题:[模板]字符串哈希

注意,“hash"已在string头文件中被定义


KMP

KMP算法是由 D.E.Knuth、J.H.Morris 和 V.R.Pratt 共同提出的一种可以在线性时间复杂度内解决模式串匹配问题的算法。用于解决 \(𝑆\) 串在 \(𝑇\) 串中出现的次数和位置。

KMP算法在程序上分为两步:

  1. 对字符串 𝑆 进行自我匹配,即求出 \(𝑛𝑒𝑥𝑡\) 数组,\(𝑛𝑒𝑥𝑡(𝑖)\) 表示 \(𝑆\) 中以 \(𝑖\) 为结尾的真子串,与 \(𝑆\) 的前缀能够匹配的最大长度。
  2. 对字符串 \(S\), \(𝑇\) 进行匹配,求出 \(𝑓\) 数组,\(𝑓(𝑖)\) 表示 \(𝑇\) 中以 \(𝑖\) 为结尾的子串,与 \(𝑆\) 的前缀能够匹配的最大长度。

next数组

\(𝑛𝑒𝑥𝑡(𝑖)\)的本质为前 \(𝑖\) 个字母的最长公共前后缀。即有 \(𝑛𝑒𝑥𝑡(𝑖)=\max{𝑗}\),其中 \(𝑗 < 𝑖\) 且有 \(𝑆(𝑖−𝑗+1\sim𝑖) = 𝑆(1\sim𝑗)\)
我们称满足上述条件的 \(𝑗\)\(𝑛𝑒𝑥𝑡(𝑖)\)候选项
考虑 \(𝑛𝑒𝑥𝑡\) 数组的求法。根据定义,有 \(𝑛𝑒𝑥𝑡(1) = 0\)
引理\(j_0\)\(𝑛𝑒𝑥𝑡(𝑖)\) 的一个候选项,则小于 \(𝑗_0\) 的最大的 \(𝑛𝑒𝑥𝑡(𝑖)\)的候选项是 \(𝑛𝑒𝑥𝑡(𝑗_0)\)
如果一个整数 \(𝑗\)\(𝑛𝑒𝑥𝑡(𝑖)\) 的候选项,那么 \(𝑗−1\) 一定是 \(𝑛𝑒𝑥𝑡(𝑖 −1)\) 的候选项。

f数组

由于 \(𝑓\)\(𝑛𝑒𝑥𝑡\) 定义相似,只需参考 \(𝑛𝑒𝑥𝑡\) 数组求出的方法即可求出 \(𝑓\) 数组。
例题:[模板]KMP

如果 \(𝑓(i) = |𝑆|\)即为出现的终止位置。


字典树 Trie

字典树是一类 \(𝐾\) 叉树(\(𝐾\)为字符集大小),用于字符串的快速检索。
每个结点都有 \(𝐾\) 个字符指针,分别对应字符集中的每个字符。在插入/检索字符串的过程中,扫描到字符 \(𝑐\),就沿着对应的字符指针向下走即可。
一颗Trie在初始时只有一个空结点,其所有字符指针均指向空。

插入

当需要往字典树中插入字符串 𝑆 时,取指针 𝑃 指向字典树的根结点。
考虑 \(𝑆\) 中的第一个字符 \(𝑆(1)\),如果根结点对应 \(𝑆(1)\) 的字符指针为空,则新建一个结点。指针 \(𝑃\) 移动至该字符指针指向的结点。接着考虑下一个字符 \(𝑆(2)\),重复上述步骤。
当字符串 \(𝑆\) 扫描完毕,在最后所在的结点上打上终止标记即可。

查询

查询的过程和插入的过程类似。
依次扫描每个字符,如果对应的字符指针不存在,则字典树中没有这个字符串。
当扫描到字符串末尾,如果当前所在的结点有终止标记,则这个字符串曾经被插入到字典树中。
例题:[模板]字典树 Trie

多测需要循环清空

posted @ 2022-08-03 21:55  micawl  阅读(24)  评论(0编辑  收藏  举报