扩展KMP(Z函数)

给出两个字符串 (S)(T),长度分别为 (n)(m),要求求出两个数组 (A)(B)

(A)(T) 的每一个后缀与 (T) 的最长公共前缀长度,(B)(S) 的每一个后缀与 (T) 的最长公共前缀长度。

我们设我们有一个数组 ( ext{extend}),其中 ( ext{extend}[i]) 表示 (S[idots n])(T) 的最长公共前缀长度。

( ext{extend}) 即为我们所求的 (B)

那么,我们如何来求这个 ( ext{extend}) 呢?先来看一组样例:

我们求出 ( ext{extend}[1]) 之后,若暴力求 ( ext{extend}[2]),会差不多重新扫描一遍字符串。

当字符串的长度足够长时,时间开销会很大。所以我们要想办法避免重复扫描一些已经确定相等的字符串。

这里我们设另一个数组 ( ext{next}),其中 ( ext{next}[i]) 表示 (T[idots m])(T) 的最长公共前缀长度。也就是题目中所求 (A)

在上面这张图中,我们可以看到,( ext{next}[2]=18),即 (T[1dots 18]=T[2dots 19])

又因为 (S[1dots 19]=T[1dots 19]),所以 (S[2dots 19]=T[2dots 19]),所以 (T[1dots 18]=S[2dots 19])

说明了什么?说明我们在求 ( ext{extend}[2]) 的时候,它的前 (18) 位都已经匹配成功了,直接从 (S[20])(T[19]) 开始匹配就可以了。

这也是 KMP 的思想,根据这个原理我们来研究扩展 KMP(Z 函数)。

[]

扩展 KMP 求 ( ext{extend}) 有两种情况。

先看一张图:

在这张图中,我们设上面的串为 (S),下面的串为 (T)

此时,在 (iin [1,k])( ext{extend}[i]) 已结完全求得。并且 (P=max{ ext{extend}[i]+i}),我们令这个得到最大值的 (i) 等于 (P0)

然后我们令 (b=k-P0+2),并记 (L= ext{next}[b])( ext{next}) 的定义同上。

接下来分析两种情况:

[]

情况一 (k+L<P)

如图所示,此时绿线 (=) 蓝线 (=) 红线 (=L)

因为 ( ext{extend}[P0] + P0 = P),根据 ( ext{extend}) 的定义,我们可知 (S[k+1dots P]=T[k-P0+2dots P-P0+1]),所以绿线 (=) 红线。

又因为 (L= ext{next}[b]),所以 (T[1dots L] = T[bdots b+L-1]),所以红线 (=) 蓝线。

也就是说,此时 ( ext{extend}[k+1]= ext{next}[b]=L)。而 (k+1) 刚好是我们现在要求的值。

这一部分的代码如下:

if(i+nxt[i-p0]<extend[p0]+p0) extend[i]=nxt[i-p0];
//i 就是 k+1,extend[p0]+p0 就是 p,nxt[i-p0] 就是 L。
//代码中下标是从 0 开始的,所以左侧不需要减去 1。

[]

情况二 (k+Lge P)

如图所示,此时蓝线 (=) 红线 (=L)

我们假设 (L-(P-k)=x),也就是这三条线末端被框起来的部分为 (x)

倘若这三个长为 (x) 的部分不存在,那么绿线 (=) 蓝线 (=) 红线 (=P-k)。理由同情况一。

那么我们假设这三条线各延伸 (x) 长度后,绿线 (=) 蓝线 (=) 红线仍然成立。

但是剩下的这 (x) 我们无从得知,所以需要暴力匹配。因为前面大部分的已经可以直接得出相等了,所以后面的 (x) 不会很大,时间开销也不会很大。

这一部分的代码如下:

int now=extend[p0]+p0-i;now=max(now,0*1ll);//now 就是我们需要延伸的 L
while(t[now]==s[now+i]&&now<m&&now+i<n) now++;//延伸过程
extend[i]=now;p0=i;

所以 ( ext{extend}) 就可以求出来了。

[]

但是我们好像忘了另一件事——( ext{next}) 还没求出来呢。

不过我们看一下它们的定义,( ext{extend}) 求的是 (S) 的后缀与 (T) 的关系,( ext{next}) 求的是 (T) 的后缀与 (T) 的关系。

看上去差不多。所以我们让 (T) 和自己跑一遍这个过程就可以求出来了。

但是注意我们要先单独求出第二位的 ( ext{next}) 再从第二位开始暴力,为的是防止求第一位时引用自己的值。

如此,扩展 KMP(Z 函数)的时间复杂度为 (Theta(n))

总代码如下:

#include<queue>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define maxn 20001000
#define INF 0x3f3f3f3f
#define int long long

using namespace std;

int q,n,m,ans1,ans2;
char s[maxn],t[maxn];
int nxt[maxn],extend[maxn];
//nxt[i] 表示 T[i ~ m] 与 T[1 ~ m] 的最长公共前缀长度 
//extend[i] 表示 S[i ~ n] 与 T[1 ~ m] 的最长公共前缀长度 

int read(){
    int s=0,w=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')s=(s<<1)+(s<<3)+ch-'0',ch=getchar();
    return s*w;
}

void Getnxt(){
    nxt[0]=m;//T 与 T 的最长公共前缀是它自己 
    int now=0,p0=1;
    while(t[now]==t[1+now]&&now+1<m) now++;
    nxt[1]=now;
    for(int i=1;i<m;i++){
        if(i+nxt[i-p0]<nxt[p0]+p0) nxt[i]=nxt[i-p0];
        else{
            int now=p0+nxt[p0]-i;now=max(now,0*1ll);
            while(now+i<m&&t[now]==t[now+i]) now++;
            nxt[i]=now;p0=i;
        }
    }     
}

void Exkmp(){
    Getnxt();//处理 nxt[]  
    int now=0,p0=0;
    while(s[now]==t[now]&&now<m&&now<n) now++;
    extend[0]=now;
    for(int i=1;i<n;i++){
        if(i+nxt[i-p0]<extend[p0]+p0) extend[i]=nxt[i-p0]; 
        //nxt[i-p0] 是 L
        //extend[p0]+p0 是 p
        //i 是 k + 1 
        //下标从 0 开始不用减 1. 
        else{
            int now=extend[p0]+p0-i;now=max(now,0*1ll);
            while(t[now]==s[now+i]&&now<m&&now+i<n) now++;
            extend[i]=now;p0=i;
        }
    }
} 

signed main(){
    scanf("%s%s",s,t);
    n=strlen(s);m=strlen(t);Exkmp();
    for(int i=0;i<m;i++)ans1^=(i+1)*(nxt[i]+1);
    for(int i=0;i<n;i++)ans2^=(i+1)*(extend[i]+1);
    printf("%lld
%lld
",ans1,ans2);
    return 0;
}

原文地址:https://www.cnblogs.com/KnightL/p/14867292.html