前言
一个很冷门的算法,而且相信要不是它出现在了(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;
}