HELLO WORLD--一起加油(🍺)!|

kingwzun

园龄:3年6个月粉丝:111关注:0

2022-09-05 15:27阅读: 56评论: 0推荐: 0

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的变化而移动。

举例
image
第8位的c的Z BOX是bacb
第9位的b的Z BOX是ba

求解过程

使用Z Box求Z数组
在知道 i1 位置Z Box的情况下,求 z[i]i 位置的Z Box。

  • 右边紫色部分表示i-1的 Z Box
  • 左边紫色部分表示i-1的 Z Box 在前缀的映射

image

分三类情况讨论

z[il+1]<ri+1
也就是i在前缀的映像的z函数小于Z Box的右边界时。
image
因为str[i-l+1,r-l+1] 等价于 str[i,r]
显然此时z[i]=z[il+1] , Z Box 不变

z[il+1]ri+1
也就是i在前缀的映像的z函数大于Z Box的右边界时。
image
和上面类似:

  • 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
image
因为此时str[i]和其他前缀没有关联,所以只能暴力算:

  • 从i往后逐个判断
  • Z Box变成[i,i+z[i]1]

为什么Z Box的右端点为什么要靠右?
增加z[il+1]<ri+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[il+1]<ri+1
image

  • ext[i] = z[i-1+1]
  • Extand Box不变

z[il+1]ri+1
image

  • ext[i]从r开始扫描
  • Extand Box变成[i,i+ext[i]1]

i > r

image

  • ext[i]从i开始扫描
  • Extand Box 变成[i,i+ext[i]1]

为什么Extand Box的右端点为什么要靠右?
增加z[il+1]<ri+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];
}
}
}

模板题

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

代码

#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;
}

习题
image

本文作者:kingwzun

本文链接:https://www.cnblogs.com/kingwz/p/16658336.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   kingwzun  阅读(56)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起