Z函数 & 扩展KMP
Z 函数
求一个字符串str 所有 以第i位开始的后缀与str的最长公共前缀(lcp)
时间复杂度: \(O(n)\)
理解
使用 z[i]表示第i位开始的后缀与str的最长公共前缀的长度
使用Z Box快速计算Z数组
Z Box是字符串str中的一个区间\([l,r]\)满足\(str[l,r]\)是str的前缀 且满足以下要求:
- 在位置i时,\([l,r]\)必须包含 \(i\)
- \(r\)尽可能大
也就是说 Z Box[i]是包含i的右边界最大的公共前缀的长度。
注意:
- 不是求 包含i的最长的前缀,而是要求包含i的右边界最大的前缀。
- Z Box会随着i的变化而移动。
举例
第8位的c的Z BOX是bacb
第9位的b的Z BOX是ba
求解过程
使用Z Box求Z数组
在知道 \(i-1\) 位置Z Box的情况下,求 \(z[i]\) 和 \(i\) 位置的Z Box。
- 右边紫色部分表示i-1的 Z Box
- 左边紫色部分表示i-1的 Z Box 在前缀的映射
分三类情况讨论
当\(z[i-l+1] < r-i+1\)时
也就是i在前缀的映像的z函数小于Z Box的右边界时。
因为str[i-l+1,r-l+1] 等价于 \(str[i,r]\)
显然此时\(z[i]=z[i-l+1]\) , Z Box 不变
当\(z[i-l+1] ≥ r-i+1\)时
也就是i在前缀的映像的z函数大于Z Box的右边界时。
和上面类似:
- str[i,r]肯定与前缀相等,因此从r+1逐个往后判断
- Z Box变成\([i , i+z[i]-1]\)
代码:
z[i] = r-i;
while(str[ i+z[i] ] == str[ 1+z[i] ])
z[i]++;
l = i, r = i+z[i]-1;
当\(i > r时\)
因为此时\(str[i]\)和其他前缀没有关联,所以只能暴力算:
- 从i往后逐个判断
- Z Box变成\([i , i + z[i]-1 ]\)
为什么Z Box的右端点为什么要靠右?
增加\(z[i-l+1] < r-i+1\)的情况。
时间复杂度:
因为:
- 求z[i]需要从1到n遍历,O(n)
- Z Box右端点最多右移n次,O(n)
所以:总体复杂度:O(n)
代码
字符串下标从1开始
void get_z(int n,string str){
int l=0,r=0;//Z Box范围
z[1]=n;
for(int i=2;i<=n;i++){
//情况3
if(i>r){
while(str[ i+z[i] ]==str[1+z[i]])
z[i]++;
l=i,r=i+z[i]-1;
}
//情况1
else if(z[i-l+1] < r-i+1){
z[i]=z[i-l+1];
}
//情况2
else{
z[i]=r-i;
while(str[i+z[i]] == str[1+z[i]])
z[i]++;
l=i, r=i+z[i]-1;
}
}
}
模板代码
string t;
int tlen;
int z[N];
void get_Z(){
int l=0,r=0;
z[0]=tlen;
for(int i=1;i<tlen;i++){
if(i>=r){
while(i+z[i]<tlen && t[i+z[i]]==t[z[i]])
z[i]++;
l=i,r=i+z[i];
}
else if(z[i-l]<r-i)
z[i]=z[i-l];
else{
z[i]=r-i;
while(i+z[i]<tlen && t[i+z[i]]==t[z[i]])
z[i]++;
l=i,r=i+z[i];
}
}
}
扩展KMP
给两个字符串str1,str2,求出str1的每一个后缀与str2的最长公共前缀
也就是说:设extend数组:extend[i]表示T与S[i,n-1]的最长公共前缀,要求出所有extendi。
时间复杂度: \(O(n)\)
求解过程
类比Z函数
- 如果str=str2,显然就是Z函数
- 如果不相等, 使用类似于Z函数的方法
类似的Box
Extand Box表示:
字符串str1中的一个区间[1,r],满足str1[1,r]是str2的前缀且满足以下要求:
- 在位置i时,[1,r]必须包含i
- r尽可能大
类似的分类讨论:
当\(z[i-l+1] < r-i+1\)时
- ext[i] = z[i-1+1]
- Extand Box不变
当\(z[i-l+1] ≥ r-i+1\)时
- ext[i]从r开始扫描
- Extand Box变成\([i,i+ext[i]-1]\)
\(当\)i > r\(时\)
- ext[i]从i开始扫描
- Extand Box 变成\([i,i+ext[i]-1]\)
为什么Extand Box的右端点为什么要靠右?
增加\(z[i-l+1] < r-i+1\)的情况,节省时间。
代码:
字符串下标从1开始
void get_text(int n,string str1,int m,string str2){
int l=0,r=0;//Box范围
for(int i=1;i<=n;i++){
//情况3
if(i>r){
while(i+ext[i]<=n && 1+ext[i]<=m&&
str1[ i+ext[i] ]==str2[1+ext[i]])
ext[i]++;
l=i,r=i+ext[i]-1;
}
//情况1
else if(ext[i-l+1] < r-i+1){
ext[i]=ext[i-l+1];
}
//情况2
else{
ext[i]=r-i;
while(i+ext[i]<=n && 1+ext[i]<=m&&
str1[ i+ext[i] ]==str2[1+ext[i]])
ext[i]++;
l=i, r=i+ext[i]-1;
}
}
}
模板代码
const int N=2e7+10;
string s,t;
int slen,tlen;
int z[N];
int ext[N];
void get_Z(){
int l=0,r=0;
z[0]=tlen;
for(int i=1;i<tlen;i++){
if(i>=r){
while(i+z[i]<tlen && t[i+z[i]]==t[z[i]])
z[i]++;
l=i,r=i+z[i];
}
else if(z[i-l]<r-i)
z[i]=z[i-l];
else{
z[i]=r-i;
while(i+z[i]<tlen && t[i+z[i]]==t[z[i]])
z[i]++;
l=i,r=i+z[i];
}
}
}
void get_exKMP(){
int l=0,r=0;
while (r < slen && r < tlen && s[r] == t[r])
r++;
ext[0] = r;
// r=0;
for(int i=1;i<slen;i++){
if(i>r){
while(i+ext[i]<slen && ext[i]<tlen && s[i+ext[i]]==t[ext[i]])
ext[i]++;
l=i,r=i+ext[i];
}else if(z[i-l]<r-i) ext[i]=z[i-l];
else {
ext[i]=r-i;
while(i+ext[i]<slen && ext[i]<tlen && s[i+ext[i]]==t[ext[i]])
ext[i]++;
l=i,r=i+ext[i];
}
}
}
模板题
代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e7+10;
string s,t;
int slen,tlen;
int z[N];
int ext[N];
void get_Z(){
int l=0,r=0;
z[0]=tlen;
for(int i=1;i<tlen;i++){
if(i>=r){
while(i+z[i]<tlen && t[i+z[i]]==t[z[i]])
z[i]++;
l=i,r=i+z[i];
}
else if(z[i-l]<r-i)
z[i]=z[i-l];
else{
z[i]=r-i;
while(i+z[i]<tlen && t[i+z[i]]==t[z[i]])
z[i]++;
l=i,r=i+z[i];
}
}
}
void get_exKMP(){
int l=0,r=0;
while (r < slen && r < tlen && s[r] == t[r])
r++;
ext[0] = r;
// r=0;
for(int i=1;i<slen;i++){
if(i>r){
while(i+ext[i]<slen && ext[i]<tlen && s[i+ext[i]]==t[ext[i]])
ext[i]++;
l=i,r=i+ext[i];
}else if(z[i-l]<r-i) ext[i]=z[i-l];
else {
ext[i]=r-i;
while(i+ext[i]<slen && ext[i]<tlen && s[i+ext[i]]==t[ext[i]])
ext[i]++;
l=i,r=i+ext[i];
}
}
}
void solve(){
cin>>s>>t;
slen=s.length(),tlen=t.length();
get_Z();
get_exKMP();
int ans=0;
for(int i = 0; i < tlen; i++) //要注意下标从 0 开始
{
ans ^= (i + 1) * (z[i] + 1);
// cout<<i<<" "<<z[i]<<" "<<endl;
}
cout << ans << "\n";
ans = 0;
for(int i = 0; i < slen; i++)
{
ans ^= (i + 1) * (ext[i] + 1);
// cout<<i<<" "<<ext[i]<<" "<<endl;
}
cout << ans;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
// cin>>t;
while(t--)
solve();
return 0;
}
习题