KMP

定义

约定记号

\(s[l,r]\) 代表从 \(l\)\(r\) 的子串。

\(\pi\) 函数

对于一个字符串 \(s\),它的 \(\pi\) 函数是它的前缀和后缀相等的最长长度。

\[\pi=\max_{s[1,i]=s[n-i+1,n]} i \]

进一步,我们定义 \(\pi_i\) 是字符串前 \(i\) 个字符组成的子串的 \(\pi\) 函数值。特别的,定义 \(\pi_1 =0\)

下面我们考虑如何求出 \(\pi\) 数列。

对于 \(\pi_1=0\) 是确定的,考虑递推。

假设我们当前求出了 \(\pi_1,\pi_2,\cdots,\pi_i\) 要求出 \(\pi_{i+1}\)

有一个特殊情况,如果 \(s[\pi_i+1]=s[i+1]\),那么 \(pi_{i+1}=\pi_i+1\)

根据 \(\pi\) 函数定义,\(s[1,\pi_i]=s[i-\pi_i+1,i]\),那么如果 \(s[\pi_i+1]=s[i+1]\),就有了 \(s[1,\pi_i+1]=s[i-\pi_i+1,i+1]\),进而有 \(\pi_{i+1}=\pi_i+1\)

下面考虑当 \(s[i+1]\ne s[\pi_i+1]\) 时的情况:

还是一样的,\(s[1,\pi_i]=s[i-\pi_i+1,i]\)\(\pi_{i+1}\) 肯定不比 \(\pi_i\) 大,所以我们要在 \([1,\pi_i]\) 中找到一个最大的 \(j\) 使得 \(s[i-j+1,i+1]=s[1,j+1]\),此时 \(\pi_{i+1}=j+1\)

注意到此时 \(s[1,j]\)\(s[1,\pi_i]\) 的前缀,\(s[i-j+1,i]\)\(s[i-\pi_i+1,i]\) 的后缀,又因为 \(s[1,\pi_i]=s[i-\pi_i+1,i]\),所以 \([i-j+1,i]\)\(s[1,\pi_i]\) 的后缀。

当满足 \(s[i-j+1,i+1]=s[1,j+1]\)\(j\) 最大时,也就是 \(s[1,\pi_i]\) 的前缀和后缀相等且长度最长的时候,我们发现此时正好和 \(\pi\) 的定义对上了,那么此时的 \(j\) 就是 \(\pi_{\pi_i}\)

若此时 \(s[j+1]=s[i+1]\),那么 \(\pi_{i+1}=j+1\)。否则一直这么找下去,直到真的没有相同的前后缀。

\(\pi\) 数组代码:

pi[1]=0;
for(int i=2;i<=n;i++){
    int j=pi[i-1];
    while(j&&s[j+1]!=s[i])j=pi[j];
    if(s[i]==s[j+1])j++;
    pi[i]=j;
}
posted @ 2024-04-03 11:55  Linge_Zzzz  阅读(11)  评论(0)    收藏  举报  来源