KMP总结
感觉实力已经达到了不会再写错KMP的地步了,但是还是记一下。
border:若字符串\(s\)的真前缀\(pre\)与真后缀\(suf\)满足\(pre=suf\),则称之为\(s\)的一个border。
周期:对于整数\(p=1,2,3,\dots,|s|\),若对于\(\forall i\in[1,|s|-p]\)都有\(s_i=s_{i+p}\),就称\(p\)为\(s\)的周期。
重点在于重复利用已经求出的信息来降低复杂度。
定义\(fail_i\)表示前缀\(pre_{s,i}\)的最长border的长度。
显然若\(t\)是\(s\)的border,那么\(t\)的border也是\(s\)的border。
现在考虑递推地求\(fail_i\)。观察到\(pre_{s,i}\)的最长的border是\(pre_{s,i-1}\)的某一个border加上\(s_i\)。那么就可以想到不断跳\(pre_{s,i-1}\)的border并检查加上\(s_i\)后是否为\(pre_{s,i}\)的border。这样就求出了\(fail_i\)。
可以证明这是\(O(n)\)的,但我不会证。
那么得到\(fail_i\)后做各种事情就很方便了。
\(fail_i\)也告诉了我们第\(i\)位失配后应该跳到哪里。
void getfail(){
fail[0]=fail[1]=0;
for(int i=2,j=0;s[i];++i){
while(j&&s[i]!=s[j+1]) j=fail[j];
if(s[i]==s[j+1]) j++;
fail[i]=j;
}
}