扩展 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;
}