首页 > 技术文章 > 扩展KMP(Z函数)

harryzhr 2021-04-20 21:11 原文

用途

\(O(n)\)求一个串与另一个串的每一个后缀的LCP长度

算法过程

其实个人感觉跟KMP关系不大,反倒与Manacher有异曲同工之妙

求解Z函数

定义

s(i,j):字符串\(S\)的第\(i\)为到第\(j\)

suf(i):以 \(i\) 开头的后缀,即\(s(i,n)​\)

z[i]:s(1,n)与\(suf(i)\)的LCP长度,\(z[1]=n\)

r:当前匹配过的最靠右的点

考虑已知\(z[1]\sim z[i-1]\),求出\(z[i]\)

第一种情况

\(i>r\),直接从\(0\)开始暴力匹配

第二种情况

当前\(s(l,r)\)对应匹配\(s(1,r-l+1)\),如图中蓝色部分所示

\(i\)对应的位置是\(i-l+1\),如果\(z[i-l+1]\)不超过\(r-i+1\),那么\(z[i-l+1]\)一定是合法的,直接赋值

第三种情况

\(i\)对应的位置是\(i-l+1\),如果\(z[i-l+1]\)超过\(r-i+1\),那么\(r-i+1\)以内的一定是合法的,剩下的需要暴力判一判

代码

inline void getz(char *s,int n){
	memset(z+1,0,n<<2);
    z[1]=n;len=n;
	for(int i=2,l=0,r=0;i<=n;++i){
		if(i<=r)z[i]=min(z[i-l+1],r-i+1);
		while(i+z[i]<=n&&s[i+z[i]]==s[z[i]+1])++z[i];
		if(i+z[i]-1>r)l=i,r=i+z[i]-1;
	}
}

求一个串与另一个串的每一个后缀的LCP长度

与求Z函数类似

inline void calc(char *s,char *a,int n,int *ans){
	memset(ans+1,0,n<<2);
	for(int i=1,l=0,r=0;i<=n;++i){
		if(i<=r)ans[i]=min(z[i-l+1],r-i+1);
		while(i+ans[i]<=n&&ans[i]+1<=len&&a[i+ans[i]]==s[ans[i]+1])++ans[i];
		if(i+ans[i]-1>r)l=i,r=i+ans[i]-1;
	}
}

例题

CF1313E Concatenation with intersection

\(f_i\)表示\(a[i]\)\(s\)的LCP长度,\(g_i\)表示\(b[i]\)\(s\)的LCS长度,这两个可以\(O(n)\)使用exKMP求

转化题意为满足以下四个条件的\(f_i+g_j-m+1\)之和

  • \(i\le j\)
  • \(i+m-1>j\)
  • \(f_i>0,g_j>0\)
  • \(f_i+g_j\ge m\)

对第二个限制容斥一下,先不考虑第二个限制,减去\(j\ge i+m-1\)的贡献

考虑维护\(g_j\ge \max(1,m-f_i)\)\(f_i+g_j-m+1\)之和

\(f_i\)\(g_j\)拆开,两个树状数组\(T1,T2\),分别维护\(g_j\ge \max(1,m-f_i)\)\(g_j\)之和的\(j\)的个数,满足\(g_j\ge \max(1,m-f_i)\)\(g_j\)之和

贡献为\((f_i-m+1)\times T1+T2​\)

注意到\(a/b\)不能把\(s\)占满,求出的\(LCP/LSP\)要与\(m-1\)\(\min\)

推荐阅读