Hash

计算哈希

$\text{Hash}(S)=(\sum_{i=1}^{|S|}{S[i]\cdot b^{|S|-i}})\bmod m$ 先预处理出 bb 的幂,递推出 SS 的所有前缀的 hash 值,然后就可以 O(1)O(1) 得到 SS 任意字串的 hash 值。

哈希冲突

生日悖论

根据不严谨的计算,当 modmodn2n^2 同阶,hash 冲突概率约为 50%

双 Hash

SS 用两个不同的模数取模,两个 hash 值都相等才认为字符串相等,通常可以令 SS 的最终 hash 值为两个 hash 值的乘积。 相较于自然溢出,双 hash 常数较大 听说有人想在 Codeforces 用自然溢出

哈希应用

判定字符串相同

hash 值相等即为相同

字符串比大小

每次把字符串分别劈成两半,如果前一半 hash 值相同就继续比较后一半 hash 值,否则继续比较前一半 hash 值,直到串长为 1,hash 值大的显然更大

lcplcp (longest common prefix)

显然可以二分

回文子串

把字符串 SS 正着 hash 一遍,倒着 hash 一遍,枚举回文中心,二分回文半径即可

乱搞

字符串题不会做可以考虑 hash 乱搞,通常题目也会给 hash 设置部分分

暴力优化系列

Manacher

用途

对每个位置 iSi∈|S|,求出 a[ia[i] 表示 s[ia[i]+1i+a[i]1]s[i−a[i]+1…i+a[i]−1] 为一段极长的回文子串。

算法

  1. 由于回文中心可能在两个字符之间,所以对原串 SS 进行预处理,在两个字符之间插入特殊字符,特别地,头尾要另外添加各不相同的另外两种特殊字符以避免边界问题
  2. 枚举 1S1\ldots|S|,过程中维护历史 i+a[i]i+a[i] 的最大值 bb 以及取到该最大值的下标 pp
  3. 对于每次枚举,求解 a[i]a[i] 方式如下:
    1. \begin{cases}

a[i]=1& (otherwise)\ \end{cases}$$

  1. 暴力更新 a[i]a[i]
  2. 维护 b,pb,p

复杂度分析

显然复杂度瓶颈是第 2 步的暴力更新次数,在第 1 步之后,若 a[i]a[i] 取 a[p2i]a[p∗2−i],那么说明此时的 a[i]a[i] 已经是正确的了,不可能再变大,第 2 步的循环不会执行,否则 a[i] 取 bib−i11,则 bi+a[i]b\leq i+a[i],则第 2 步中每执行一次循环 a[i]+ia[i]+i 均变大 1,再在第 3 步中更新到 bb,执行了多少轮循环,bb 就增加多少,而 bb 最大为 S|S|,因此第 2 步的循环在整个算法中只会执行 O(S)O(|S|) 次。

Z Function

用途

SS 的每个位置 iSi∈|S|,求出 z[i]z[i] 表示 S[ii+z[i]1]S[i\ldots i+z[i]-1] 是极大的 SS 的前缀,即 lcplcp。 对 TT 的每个位置 iTi∈|T|,求出 d[i]d[i] 表示 T[ii+z[d]1]T[i\ldots i+z[d]-1] 是极大的 SS 的前缀。

算法

  1. 枚举 2S2\ldots|S|,过程中维护历史 i+z[i]i+z[i] 的最大值 bb 以及取到该最大值的下标 pp
  2. 对于每次枚举,求解 z[i]z[i] 方式如下:
    1. \begin{cases}

z[i]=0& (otherwise)\ \end{cases}$$

  1. 暴力更新 z[i]z[i]
  2. 维护 b,pb,p
  3. 特别地,根据定义不同,z[1]=Sz[1]=|S|00
  4. 求解 dd 的方式同理,且下标从 1 开始枚举

复杂度分析

同理:[[!字符串总结#Manacher#复杂度分析]]

DFA 系列

DFA (确定有限状态自动机)

DFA 构成

  1. 状态集 QQ
  2. 可接受状态集 qAq_A
  3. 初始状态 q0q_0
  4. 字符集 CC
  5. 转移函数 δ(q,c)\delta(q,c)

如何自动

一开始,状态位于起始状态 q0q_0,接下来,将待判断的串的每个字符依次输入 每输入一个字符 cc,就将当前状态从 qq 移动至 δ(q,c)δ(q,c)。 若将给定串的所有字符输入完毕后,最终所在的状态状态 qendqAq_{end}∈q_A,则输入的串是可接受串。

Trie

Q=树节点Q=树节点 qA=字符串结尾字母所在节点q_A=字符串结尾字母所在节点 q0=0号节点q_0=0\,号节点 C=dependsC={depends} δ(q,c)=q代表的字符串后面加上字符c代表的字符串所在节点\delta(q,c)=q\,代表的字符串后面加上字符\,c\,代表的字符串所在节点

KMP

对于字符串 TT,设 rightTright_TTT 相等前后缀的长度的集合 观察到“在 TT 后输入什么样的字符串才能让 TT 变为可接受字符串只与 rightTright_T 有关 设 q=rightTq=right_T 观察到只要确定 qq 中最大元素就可以确定整个 qq,于是 设 q=max{ xxq }q^{'}=\max \{ \ x|x\in q \ \}
设 $fail[q^{'}]=\max ⁡\{\ x|x\in q \quad \&\& \quad x<q^{'}\ \}$ Q={ q  q 1,2,3,S}Q=\{ \ q \ |\ q^{'}\in \ 1,2,3\ldots,|S| \} qA={ q  q =S}q_A=\{ \ q \ |\ q^{'}\ = |S| \} q0={ q  q =0}q_0=\{ \ q \ |\ q^{'}\ = 0 \} C=dependsC=depends 有转移

$$fail[q^{'}]=\begin{cases} 0&(q^{'}=1)\\ \delta(fail[q^{'}-1],c)&(otherwise) \\ \end{cases}$$$$\delta(q^{'},c) \begin{cases} q^{'}+1&(q^{'}<|S| \quad \& \& \quad S[q^{'}+1]=c ) \\ 0& (q^{'}=0 \quad \& \& \quad S[q^{'}+1]\neq c)\\ \delta (fail[q^{'}],c)&(otherwise) \\ \end{cases}$$

ACAM(Aho_corasick Automaton)

与 KMP 类似地,设 H={可能成为某个模式串的前缀的字符串的集合}H=\{可能成为某个模式串的前缀的字符串的集合\}q=rightT={ S  S 是 T 的后缀 且 SH}q=right_T=\{\ |S|\ |\ S\ 是\ T\ 的后缀\ 且 \ S\in H\}q={  S  S=max(Sq) }q^{'}=\{ \ \ S\ |\ \quad |S|=\max (|S|\in q)\ \}
fail[q]={ q 中第二长的字符串}fail[q^{'}]=⁡\{\ q\ 中第二长的字符串\} Q={ q  qS 的前缀}Q=\{ \ q \ |\ q^{'}\in S \ 的前缀 \} qA={ q  q =S}q_A=\{ \ q \ |\ q^{'}\ = S \} q0={ q  q =}q_0=\{ \ q \ |\ q^{'}\ = \emptyset \} C=dependsC=depends 有转移

$$fail[q^{'}]=\begin{cases} \emptyset &(|q^{'}|=1)\\ \delta(fail[fa[q^{'}]],c)&(otherwise) \\ \end{cases}$$$$\delta(q^{'},c) \begin{cases} q^{'}+c&(q^{'}<|S| \quad \& \& \quad q^{'}+c\in H) \\ \emptyset & (q^{'}=\emptyset \quad \& \& \quad c\notin H)\\ \delta (fail[q^{'}],c)&(otherwise) \\ \end{cases}$$

Summary from

《信息学竞赛中的字符串问题选讲》浙江省余姚中学 诸一行