KMP
前缀函数
\(\pi_i,\pi_{\pi_i},\pi_{\pi_{\pi_i}},\pi_{\pi_{\pi_{\pi_i}}},\pi_{\pi_{\pi_{\pi_{\pi_i}}}},\pi_{\pi_{\pi_{\pi_{\pi_{\pi_i}}}}},\dots\)
一个字符串中一个最长的真前缀,并要求其和一个后缀相同。
1. 暴力
直接暴力求:
for(int i=2;i<=n;i++){
for(int j=i-1;j>=0;j--){
if(sub(s,1,j)==sub(s,i-j+1,i)){
p[i]=j;
break;
}
}
}
2. 优化
首先发现 \(\pi\) 数组每次之多增长 1,所以只需要从 \(\pi_{i-1}+1\) 开始枚举。
for(int i=2;i<=n;i++){
for(int j=p[i-1]+1;j>=0;j--){
if(sub(s,1,j)==sub(s,i-j+1,i)){
p[i]=j;
break;
}
}
}
这样的时间复杂度是 \(O(N^2)\),因为每次只增长 1,至多到 \(n-1\),减少的下限又是 0,所以层是 \(O(N)\) 的,内层 sub 是 \(O(N)\),总:\(O(N^2)\)
3. 正解
可以将 \([1,\pi_i]\) 拆成 \([1,\pi_i-1]+s_i\),所以 \([1,\pi_i-1]\) 一定是 \([1,i-1]\) 的后缀。
所以最开始找到 \(\pi_{i-1}\),若 \(s_{\pi_{i-1}+1}\not=s_i\),那么就需要 \(\pi_{\pi_{i-1}}\) 来判断,以此类推。
for(int i=2;i<=n;i++){
int j=p[i-1];
while(j&&s[j+1]!=s[i])j=p[j];
if(s[j+1]==s[i])j++;
p[i]=j;
}
字符串匹配
找到模式串 \(s\) 在文本串 \(t\) 中出现的所有位置。
法一
一个字符串,存:模式串 \(+'\#'+\) 文本串。
\(\#\) 的作用是防止匹配时长度超出模式串长度。
然后对字符串求前缀函数,当我们发现 \(\pi_i=|模式串|\) 时,说明找到了文本串中与模式串匹配的子串。
法二
题解都是这么写的。
不合并模式串和文本串,直接比较。
两个指针 \(i\) 和 \(j\),\(i\) 在文本串中,\(j\) 在模式串中。
若 \(s_{j+1}\not=t_i\),此时匹配不上了。但是前面匹配了这么多不能白匹配了,所以将 \(j\) 转移到 \(\pi_j\) 的位置继续匹配。因为 \(\pi_j\) 时前缀函数,所以 \([1,\pi_j]\) 还是能和文本串匹配上的。
总结
关于从 \(\pi_i\) 到 \(\pi_{\pi_i}\) 的过程中还是比较绕的,要理清楚。
然后就是解决问题的时候要依据 \(\pi\) 的定义以及性质,比如周期和压缩的问题。
浙公网安备 33010602011771号