zoukankan      html  css  js  c++  java
  • 扩展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;
    }
    
    
  • 相关阅读:
    OpenStack 发行版本
    刷新linux硬盘存储接口
    LVM实践
    LVM man帮助
    ansible --help 文档
    nmcli connection modify eth1 ipv4.addr "192.168.31.23" ipv4.method manual
    自己动手使用, MetaWeblog 发布博客(cnblogs)
    测试图片上传 on Markdown editor
    大批量更新数据mysql批量更新的四种方法
    PHP print_r 转换/还原为数组
  • 原文地址:https://www.cnblogs.com/KnightL/p/14867292.html
Copyright © 2011-2022 走看看