字符串相关 学习笔记
哈希和哈希表
令 \(b\) 为选取的基数(229或233等),则字符串匹配时子串 \(C'=c_{k+1}c_{k+2}\dots c_{k_n}\) 的哈希值为 \[ H(C')=H(C,k+n)-H(C,k)\times b^n \]
哈希的相关运用
- 哈希字符串匹配
转化成上面的板子就好做了。
- 哈希找回文串
正着存一组哈希值,再倒着存一组哈希值。枚举回文串的对称中心,二分回文长度(的一半),利用哈希判等。可以在 \(O(n\log n)\) 的复杂度内找最长的回文串、回文串的数量。
例题:P3501
KMP
运用两个指针 \(i,j\) 表示以 \(A[i]\) 结尾长度为 \(j\) 的字符串正好与 \(B\) 串的前 \(j\) 个字符匹配。若下一个字符匹配,则挪动指针;否则将 \(j\) 挪动到 \(fail[j]\) 处。 \(fail[j]\) 为失配数组,表示在 \(B\) 串的 \(j\) 处失配时, \(j\) 应该往前跳到的位置。
首先对 \(B\) 串预处理,得到 \(fail\) 数组,预处理过程可视为 \(B\) 串自己和自己匹配的过程。
该算法复杂度为线性的。
KMP的相关运用
- KMP求公共前后缀
在KMP中,若一个串长度为 \(n\) ,则 \(fail[n]\) 就是该串公共前后缀的长度。
- KMP求最大(最小)周期
如果一个长度为 \(n\) 的串有长度为 \(j\) 的公共前后缀,则该串必有长度为 \(n-j\) 的周期。
由此,在KMP中,若一个串长度为 \(n\) ,则 \(fail[n]\) 是最长公共前后缀,则 \(n-fail[n]\) 是该串的最小周期;在 \(j>0\) 时找 \(fail[j]\) ,最小的非0的 \(j'\) 是最短公共前后缀,则 \(n-j'\) 是该串的最大周期。
扩展KMP
求解的问题:求出一个字符串的每个后缀与一个字符串(可以是自己)的最长公共前缀。
先暴力求得 \(Z_1\) 的值,然后向后枚举。像后文的 manacher 一样维护 \(pos,maxr\),并判断 \(Z_{i-pos}\) 能否扩展到 \(maxr\) 右边,如果不能就直接更新 \(Z_i\),否则暴力更新。
失配树
求解的问题:求出一个字符串的两个前缀的最长公共前后缀。
先跑KMP,容易发现,一个字符串的最长公共前后缀就是 \(fail[n]\)。同时 \(fail[fail[n]]...\) 等都是他的最长公共前后缀。那么我们容易知道,两个字符串的公共前后缀构成树形。于是我们将 \((fail[i],i)\) 连边,会得到一棵树,这就是失配树。上面那个问题可以在失配树上求LCA解决。
AC自动机
若有一个主串需要与多个模式串匹配,需要用的AC自动机。考虑将多个模式串建立字典树,再在字典树上建立 \(fail\) 数组,即可完成匹配。
具体实现:先建立字典树,注意下标从1开始。然后将0号结点的所有边全部连向1号结点。从1开始进行广搜,对于每个结点 \(u\),枚举所有出边,若不存在,则直接将出边连向 \(fail[u]\) 的出边;否则将儿子入队,找到 \(fail[u]\) 能跳到的有和该儿子相同的结点,更新 \(fail[v]\)。
1 | void init(){ |
AC自动机相关题目处理技巧
可以先考虑只有一个模式串的情况,先按KMP的做法写,思路厘清后再甩到字典树上。
最小表示法
参考这个。思路和代码都比较简单。
1
2
3
4
5
6
7
8
9
10
11
12
13
14int solve(){//返回最小表示的初始位置
int i=1,j=2,k=0;
while(i<=n&&j<=n&&k<n){
if(num[i+k]==num[j+k]){
k++;
continue;
}
if(num[i+k]>num[j+k])i=i+k+1;
else j=j+k+1;
if(i==j)j++;
k=0;
}
return min(i,j);
}
manacher算法
先在两个字符间插入一个特殊字符,然后执行以下步骤:
1.如果 \(i<maxr_i\),按对称方法更新 \(p_i\)。
2.暴力拓展 \(p_i\)。
3.更新 \(maxr\) 和 \(pos\)。