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;
  }
}
posted @ 2025-04-18 12:53  RandomShuffle  阅读(5)  评论(0)    收藏  举报