扩展 KMP(Z 函数)学习笔记

前言

一个很冷门的算法,而且相信要不是它出现在了(NOIP2020T2),它还会继续冷门下去。

估计以后应该也碰不上这个算法的题目了,所以完全就是为了做一道题学了一个算法。。。

(Z)函数

对于一个字符串(s),定义(Z(i))表示(s)(i)个后缀(s)(LCP)长度。

至于它的求法,个人感觉明明叫(ExKMP)却和(KMP)毫无关系,反而与(Manacher)有着异曲同工之妙。

考虑从小到大枚举(i),同时维护(Mx)表示已经求解过的位置中最大的(i+Z(i)-1),并记录(id)表示一个能取到最大值的位置。(注意,(id)不能等于(1),因为(Z(1))很特殊,为(n)

也就是说,我们能够确定([id,Mx])([1,Z(id)])是完全相同的两个子串。

然后,对于当前的(i),若它大于(Mx),则直接暴力扩展。

否则,根据已知信息,我们可以得出([i,Mx])([i-id+1,Z(id)])是两个完全相同的子串。

又已知([i-id+1,(i-id+1)+Z(i-id+1)-1])([1,Z(i-id+1)])是相同的子串。

因此,经过两次转化,我们得出([i,Mx])([1,min{Z(i-id+1),Mx-i+1}])两个子串是一样的。

那么我们只要在(min{Z(i-id+1),Mx-i+1})的基础上继续尝试暴力扩展即可。

复杂度证明的话,可以发现一个位置最多被扩展到一次,因此是(O(n))的。

扩展(KMP)

扩展(KMP)就是对于两个字符串(s1,s2),求(s1)的每一个后缀和(s2)(LCP)长度。

我们先求出(s2)(Z)函数,然后以此去和(s1)匹配。

仔细想想就会发现这个匹配的过程和前面求(Z)函数的过程是几乎一样的,同样只要暴力扩展就行了,复杂度依然有保证。

模板(板子题)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 20000000
using namespace std;
int n,m;char s1[N+5],s2[N+5];
int Z[N+5];I void GetZ()//预处理Z函数
{
	RI i,id,Mx=0;for(Z[1]=m,i=2;i<=m;++i)
	{
		i<=Mx&&(Z[i]=min(Z[i-id+1],Mx-i+1));//计算Z(i)初始值
		W(i+Z[i]<=m&&s2[i+Z[i]]==s2[1+Z[i]]) ++Z[i];//暴力扩展
		i+Z[i]-1>Mx&&(Mx=i+Z[id=i]-1);//尝试更新最右点
	}
}
int p[N+5];I void ExKMP()//扩展KMP,和上面类似
{
	RI i,id,Mx=0;for(i=1;i<=n;++i)
	{
		i<=Mx&&(p[i]=min(Z[i-id+1],Mx-i+1));
		W(i+p[i]<=n&&1+p[i]<=m&&s1[i+p[i]]==s2[1+p[i]]) ++p[i];
		i+p[i]-1>Mx&&(Mx=i+p[id=i]-1);
	}
}
int main()
{
	RI i;long long t;scanf("%s%s",s1+1,s2+1),n=strlen(s1+1),m=strlen(s2+1),GetZ(),ExKMP();
	for(t=0,i=1;i<=m;++i) t^=1LL*i*(Z[i]+1);printf("%lld
",t);
	for(t=0,i=1;i<=n;++i) t^=1LL*i*(p[i]+1);printf("%lld
",t);return 0;
}
原文地址:https://www.cnblogs.com/chenxiaoran666/p/ExKMP.html