扩展 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\n",t);
	for(t=0,i=1;i<=n;++i) t^=1LL*i*(p[i]+1);printf("%lld\n",t);return 0;
}
posted @ 2020-12-10 18:50  TheLostWeak  阅读(44)  评论(0编辑  收藏