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数组
在知道 \(i-1\) 位置Z Box的情况下,求 \(z[i]\)\(i\) 位置的Z Box。

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

image

分三类情况讨论

\(z[i-l+1] < r-i+1\)
也就是i在前缀的映像的z函数小于Z Box的右边界时。
image
因为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的右边界时。
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[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\)
image

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

\(z[i-l+1] ≥ r-i+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[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];
        }
    }
}

模板题

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

posted @ 2022-09-05 15:27  kingwzun  阅读(51)  评论(0编辑  收藏  举报