​P5410 【模板】扩展 KMP(Z 函数)​

前置芝士

KMP

​洛谷模板​​ & ​​\(\texttt{My solution}\)​

问题概括

扩展 KMP:求出字符串 \(S\) 的所有后缀与 \(T\) 的最长公共前缀长度,时间复杂度 \(\mathcal{O}(|S|+|T|)\)。

该解法思想与 KMP 类似,所以称作扩展 KMP。

解法

\(next\) 数组

定义 \(next_i\) 为 \(T\) 由 \(i\) 开始的后缀与 \(T\) 的最长公共前缀长度。(\(z\) 函数)

这是本题的第一问,也是第二问的辅助数组。



求法

显然 \(next_0=|T|\),定义 \(n=|T|\)。

考虑求出了 \(next_{0\cdots x-1}\),现在要求 \(next_x\)。

注:以下的图颜色相同的部分的字串都相等。

定义 \(k=\max\{j+next_j-1\}(0\le j\lt x)\) 的 \(j\),\(p=k+next_k-1\)。

根据 \(next\) 定义,可得 \(T\) 的前缀 \(T_{0\cdots next_k-1}=T_{k\cdots n}\) 的后缀 \(T_{k\cdots p}\)。

扩展 KMP 学习笔记_KMP

我们在 \(T_{k\cdots p}\) 中把 \(T_{x\cdots p}\) 标注出来,因为两段灰色部分相同,可以在第一段里找到相同的位置 \(T_{x-k\cdots next_k-1}\)。

扩展 KMP 学习笔记_字符串_02

我们找到 \(next_{x-k}\),记作 \(y\)。由于 \(next\) 的定义,\(T_{0\cdots y-1}=T_{x-k\cdots x-k+y-1}\),在右边的灰色部分,也有一段和它完全一样的 \(T_{x\cdots x+y-1}\)。

情况一:蓝色部分小于等于红色部分,此时确定 \(next_x=y\)

扩展 KMP 学习笔记_数组_03

情况二:蓝色部分大于红色部分,此时发现 \(x+y-1\) 已经超过了我们目前去到的最远的地方,因此从 \(p,p-x+1\)(就是第一段蓝色中位置大于红色长度的那一部分) 开始一位一位比,算出真正的 \(next_x\)。(记得要更新 \(k\) 和 \(p\))

扩展 KMP 学习笔记_KMP_04

\(p\) 是我们当前已匹配到最远的位置,情况一 \(k\) 不会变,情况二 \(k\) 会变大,\(k\) 保持不降,时间复杂度 \(\mathcal O(|T|)\)



\(extend\) 数组

\(extend_i\) 数组表示 \(S\) 从 \(i\) 开始的后缀与 \(T\) 的最长公共前缀长度。(第二问)



求法

和 \(next\) 求法非常像。还是考虑求出了 \(extend_{0\cdots x-1}\),现在求 \(extend_x\)

还是定义 \(k=\max\{j+extend_j-1\}(0\le j\lt x)\) 的 \(j\),\(p=k+extend_k-1\)。

扩展 KMP 学习笔记_字符串_05

在图中画出 \(x\),\(x-k\),标红色。

扩展 KMP 学习笔记_题解_06

定义 \(y=next_{x-k}\),标出相同的三段蓝色。

扩展 KMP 学习笔记_数组_07

这时还是可以分为两种情况。蓝色部分小于等于红色部分,存在 \(extend_x=y\),否则需要从 \(p,p-x+1\) 开始一一比对确定 \(extend_x\)。

由于此时的 \(k\) 不降,时间复杂度 \(\mathcal O(|S|)\)

实现

我的代码中 \(next\to nxt,extend\to ext\)

#include<stdio.h> 
#include<string.h> 
const int maxn = 2e7 + 1; 
const int& max2(const int& a,const int& b){return a > b ? a : b;} 
char s[maxn],t[maxn]; 
int n,m,nxt[maxn],ext[maxn]; 
void calc_nxt(){ 
nxt[0] = n; 
int j = 0; 
while(j + 1 < n && t[j] == t[j + 1]) ++j; 
nxt[1] = j; // nxt[0],nxt[1] 暴力算
int k = 1; // 我是把k放在循环外,p用k得到,更新的也是k
for(int i = 2;i < n;++i){
int p = k + nxt[k] - 1; 
if(i + nxt[i - k] <= p) nxt[i] = nxt[i - k]; // 情况一
else { 
j = max2(p - i + 1,0); // 情况二
while(i + j < n && t[i + j] == t[j]) ++j; // 情况二,暴力一位一位比对
nxt[i] = j,k = i; // 记得更新k
} 
} 
} 
void calc_ext(){// 和刚才几乎一样
int j = 0; 
while(j < n && j < m && s[j] == t[j]) ++j; 
ext[0] = j; // ext[0] 暴力算
int k = 0; // 放在循环外 
for(int i = 1;i < m;++i){
int p = k + ext[k] - 1; 
if(i + nxt[i - k] <= p) ext[i] = nxt[i - k];// 省略讲解里的y
else { 
j = max2(p - i + 1,0); // 取max
while(i + j < m && j < n && s[i + j] == t[j]) ++j; // 一位一位比对
ext[i] = j,k = i; 
} 
} 
}  
int main(){ 
scanf("%s%s",s,t); 
n = strlen(t),m = strlen(s); 
calc_nxt();calc_ext(); 
long long res1 = 0,res2 = 0;
for(int i = 0;i < n;++i) res1 ^= 1LL * (i + 1) * (nxt[i] + 1); 
for(int i = 0;i < m;++i) res2 ^= 1LL * (i + 1) * (ext[i] + 1); 
printf("%lld\n%lld\n",res1,res2); 
return 0; 
} 
// 拜拜qwq~ (你以为我会让你直接复制代码?