字符串
前缀函数
给定一个字符串 \(s\),定义第 \(i\) 个位置上的前缀函数 \(fail[i]\) 表示其前缀和后缀相等的最大长度,对于每个 \(i\),求出 \(fail[i]\)。
举例:对于文本串 abcab
:
\(fail[1]=0\),我们规定如此。
\(fail[2]=0\),因为 ab
没有相等的前缀和后缀。
\(fail[3]=0\),因为 abc
没有相等的前缀和后缀。
\(fail[4]=1\),因为 abca
有一个相等的前缀和后缀 a
,长度为 \(1\)。
\(fail[5]=2\),因为 abcab
有一个相等的前缀和后缀 ab
,长度为 \(2\)。
这里我们介绍 Wilson_Inversion 大佬教我的求 fail 数组的方法:
对于位置 \(i\):
- 令 \(p=fail[i-1]\)。
- 如果 \(s[i]\ne s[p+1]\),令 \(p=fail[p]\),否则 \(fail[i]=p+1\)。
- 重复步骤 \(2\) 直到求出 \(fail[i]\)。
具体地,我们仍然使用 abcab
来演示这一步骤:
对于位置 0:
\(fail[0]=-1\),为了方便,我们规定如此。
对于位置 1:
由于 \(fail[1-1]=-1\),所以 \(fail[1]\) 被直接赋值为 \(0\)。
对于位置 2:
由于 \(fail[2-1]=0\) 且 \(s[2]\ne s[1]\),所以 \(p\) 被赋值为 \(0\)。
由于 \(fail[0]=-1\),所以 \(fail[2]\) 被直接赋值为 \(0\)。
对于位置 3:
由于 \(fail[3-1]=0\) 且 \(s[3]\ne s[1]\),所以 \(p\) 被赋值为 \(0\)。
由于 \(fail[0]=-1\),所以 \(fail[3]\) 被直接赋值为 \(0\)。
对于位置 4:
由于 \(fail[4-1]=0\) 且 \(s[4]=s[1]\),所以 \(fail[4]=1\)。
对于位置 5:
由于 \(fail[4-1]=1\) 且 \(s[5]=s[2]\),所以 \(fail[5]=2\)。
code:
int p = 0;
fail[0] = -1, fail[1] = 0;
for(int i = 2; i <= len1; i++) {
while(p != -1 && s[i] != s[p + 1]) p = fail[p];
fail[i] = ++p;
}
KMP
给定文本串 \(t\) 和模式串 \(s\),求模式串在文本串中所有出现位置。
和求 fail 数组的原理一样,只不过这次是在文本串中匹配模式串,我们按照如下方法进行:
- 令 \(p=fail[i-1]\)。
- 如果 \(t[i]\ne s[p+1]\),令 \(p=fail[p]\),否则 \(ans[i]=p+1\)。
- 重复步骤 \(2\) 直到求出 \(ans[i]\)。
这里 \(ans[i]\) 的含义是文本串的第 \(i\) 个位置的后缀与模式串的前缀的最大匹配长度,当这个长度与模式串长度相等时,代表模式串在文本串中出现了。
code:
int p = 0;
for(int i = 1; i <= len2; i++) {
while(p != -1 && t[i] != s[p + 1]) p = fail[p];
ans[i] = ++p;
}
Trie 树
图源:oi-wiki