2021.7.16 字符串讲解
By Zhang_RQ
哈希
对于一个长度为 n 的字符串,其哈希值为 \(\sum\limits_{i=1}^ns_i\times base^{n-i}\bmod p\),其中 \(base\) 和 \(p\) 自选
判断两个字符串的一个方法是直接判断哈希值是否相等(虽然说有概率出错)。
可以快速算出一个字符串任意子串的哈希值。
具体而言,预处理出前缀哈希值,然后减一减,乘 \(base\) 的若干次逆元就可以了。
在 \(10^5\) 级别时可能会出现哈希冲突,可以使用双模数。
有种特殊的模数是自然溢出。
KMP
在线性时间中完成字符串匹配的算法
内容:给定一个源串和模式串,求出模式串在源串中出现的次数及出现的位置。
核心思想:减少暴力匹配时的浪费的信息,利用 \(nxt\) 数组进行优化
\(nxt_i\) 的定义:对于以 \(i\) 结尾的前缀,满足最大的"前缀等于后缀"的长度。
\(nxt\) 求法:\(nxt_i=nxt_{i-1}\) 新加入的字符匹配 \(+1\) ,否则跳 \(nxt\)
匹配做法:暴力匹配,失配时跳 \(nxt\) 而不是从头开始。
for(ri i=1,j=0;i<=n;i++){
while(j&&s2[j+1]!=s1[i])
j=nxt[i];
if(s2[j+1]==s1[i])
j++;
if(j==m){
ans.push_back(i-m+1);
j=nxt[j];
}
}
Trie树
把节点作为状态,把字母放到边上。
用法:维护若干串,查询一个串是否是这些串的前缀。
void insert(char *s,int id){
int x=rt;
for(ri i=1;s[i];i++){
if(!son[x][s[i]-'a'])
son[x][s[i]-'a']=++cnt;
x=son[x][s[i]-'a'];
}
nd[id]=x;
}
01Trie:把数字当成二进制串插到Trie里。可以实现不少有趣的功能,比如查一个数的前驱后继(有点像平衡树?)。一个比较经典的应用是维护一个数集,每次查询时给定一个数字,要求从数集中选出来一个数,最大化或最小化两个数的异或和。
AC自动机
可以理解为在多个串上的KMP。利用Trie树来维护这些串,nxt数组变为fail指针。
Fail指针的构造思想:
可以直接构造 trie 图以进行多串匹配。
Trie图构建的代码:
il void fail(){
queue<int> q;
for(ri i=0;i<26;i++)
if(son[rt][i]){
q.push(son[rt][i]);
fail[son[rt][i]]=rt;
}
while(!q.empty()){
int x=q.front();
q.pop();
for(ri i=0;i<26;i++){
if(son[x][i]){
fail[son[x][i]]=son[fail[x]][i];
q.push(son[x][i]);
}
else
son[x][i]=son[fail[x]][i];
}
}
for(ri i=1;i<=cnt;i++)
tot[fail[i]]++;
}
Manacher
用于求解最长回文子串的算法,线性。
思想:利用之前的已知信息来优化:
- 维护当前最长的回文串和其回文中心
- 对于一个新加入的位置,从关于会问中心对称的位置继承答案。
- 这样每次更新答案的时候都是本质不同的回文串,复杂度自然就是线性的。
在字符之间添加 '#' 以处理回文中心不在字符上的情况。