KMP

一些基础定义

  • prefixs,r=s1rprefix_{s,r}=s_{1\sim r} 表示前缀; suffixs,r=ssr+1ssuffix_{s,r}=s_{|s|-r+1\sim|s|} 表示后缀;
  • asdfasasdf 是该串的一个周期p=sp=|s| 一定是 ss 的周期; 若周期 pp 满足 psp | |s| 则称 pp 为之的循环节
    • 性质:

      ppss 的周期 sp⇔|s|-pssBorder\texttt{Border}

      Border\texttt{Border} 不具有单调性

Border

定义

若字符串 ss 的同长度的前缀和后缀完全相同,即 suffixi=prefixisuffix_i=prefix_i 时,则称此前缀或后缀为一个 Border\texttt{Border}(根据语境,有时 Border\texttt{Border} 也指长度),字符串本身也是它的 Border\texttt{Border}(根据语境有时候又不是了)。

题目:若 s=bbabbabs=bbabbab,求出所有 Border\texttt{Border}

性质

  • 传递性:ssBorder\texttt{Border}Border\texttt{Border} 也是 ssBorder\texttt{Border}
  • ss 的所有 Border\texttt{Border} 等价于求 ss 所有前缀的最大 Border\texttt{Border}

KMP

nexti=prefixinext_i=prefix_i 的非平凡的最大 Border\texttt{Border}(不包含自身),next1=0next_1=0

考虑 prefixiprefix_i 的所有长度大于 11Border\texttt{Border},去掉最后一个字母,就会变成 prefixi1prefix_{i-1}Border\texttt{Border}

因此求 nextinext_i 的时候,可以遍历 prefixi1prefix_{i-1} 的所有 Border\texttt{Border},即 nexti1,nextnexti1,,0next_{i-1},next_{next_{i-1}},\dots,0,检查最后一个字符是否等于 sis_i

for(int i = 2;i <= n + 1;i++)
{
	int j = next[i - 1];
	while(s[i] != s[j + 1] && j) j = next[j];
	if(s[i] == s[j + 1]) j++;
	next[i] = j;
}

字符串匹配

给出两个字符串 sstt,求 ttss 中出现次数和所有出现的位置。

例:s=s= abababct=t= aba,则所有出现位置为 1133

  • Hash:枚举起始位置,然后用 Hash 检查。复杂度 O(n)\mathcal{O(n)},常数较大,具有一定的不确定性。
  • KMP:
    • 充分利用前缀匹配的有效信息,即 nextnext 数组(Border\texttt{Border} 的性质),进行快速转移。复杂度 O(s+t)\mathcal{O(|s|+|t|)}
    • 代码:
#include <bits/stdc++.h>
using namespace std;

int next[1000001],n,m,ans;
string s,t;

signed main()
{
	cin >> s >> t;
	n = s.size(),m = t.size();
	s = "." + s;
	t = "." + t;
	for(int i = 2;i <= n + 1;i++)
	{
		int j = next[i - 1];
		while(s[i] != s[j + 1] && j) j = next[j];
		if(s[i] == s[j + 1]) j++;
		next[i] = j;
	}
	for(int i = 1,j = 0;i <= m;i++)
	{
		while(t[i] != s[j + 1] && j) j = next[j];
		if(t[i] == s[j + 1]) j++;
		if(j == n){ans++;j = next[j];}
	}
	printf("%d",ans);
	return 0;
}