浅谈拓展kmp(z函数)

先贴一波kmpmanacher的博客,对于理解这个很有用:

https://blog.csdn.net/qq_49593247/article/details/120077670?spm=1001.2014.3001.5501

拓展kmp函数(z函数)在竞赛中比较冷门,但是学习了z函数会提升对于manacher和kmp这一类的字符串处理算法的理解.它本身其实也是按照的kmp的思想.

扩展KMP求的是对于文本串S1的每一个后缀子串与模式串S2的最长公共前缀,类似于manacher算法,它也会存一个右端点在已经匹配过的点的最右端的点.它还存在一个next数组和extend数组,next数组含义类似于kmp的next数组,所存的数据是从模式串第i位开始和它的第一位匹配匹配,所匹配的最长前缀的长度.而extend数组则是存的文本串和模式串相匹配的该结果.

例如:有文本串aaaabba和模式串aaaaa.

 为什么我们要预先处理一个next数组呢,类似于kmp算法,当在匹配过程中,模式串的前缀和文本串后缀所匹配的长度的值其实就等于模式串自己和自己匹配,因为匹配了此时两者是相等的,那么就可以在文本串与模式串匹配时直接用next来获取模式串长度为i时最长的公共前缀长度.

然后就是匹配的思想了.

我们会存一个两个字符串相等是能匹配的最长长度的起点p,并且用一个now值去记录该字符串的长度.所以一开始我们没有进行匹配,所以我们就要去求此时的起点和匹配的长度:

    int now=0;//此时没有进行匹配,长度就是0
    while(str1[now]==str[now]&&now<len1&&now<len)
        now++;//从0开始匹配,字符相匹配那么长度就增加    
    extend[0]=now;//文本串从0开始的后缀与模式串相匹配的长度
    int p=0;//记录起点

储存之后,我们从1开始匹配,匹配时就会出现两种情况:

1.i+nex[i-p]<p+extend[p]

第一种情况从当前位置i开始(此时1到n-1的nex值和extend值已经算出来了).p是之前匹配的最右串的起点,p没有更新,所以i-p就是指此时文本串和模式串已经匹配的长度,因为此时文本串的p和模式串的头是对在一起的.所以i+nex[i-p]就是指模式串从i加上此时从i开始的模式串的后缀和模式串前缀匹配的最长前缀的长度(此时p到nex[p]以及全部是相等的了,可以看做是模式串的一部分)的位置.此时extend[i]的值就是此长度(i-p)的next值,也就是截取的模式串i开始的后缀和模式串前缀相等的这段的最长公共前缀的长度.

if(i+nex[i-p]<p+extend[p])extend[i]=nex[i-p];

2.i+nex[i-p]>=p+extend[p]

当超过存的最长匹配串的右端时,因为右端没有进行匹配,说明此时i开始往后的这个匹配串会成为新的最右端匹配串,所以我们就暴力往后匹配,此处有一个类似于kmp的往后移动模式串的过程.将模式串的头对准i,然后因为之前的预处理,到p+extend[p]为止,两个串已经匹配好了,那么再往后面暴力匹配即可:

        else
        {
            now=p+extend[p]-i;
            now=max(now,0);//防止出现负数
            while(str[now+i]==str1[now]&&now+i<len&&now<len1)
            {
                now++;
            }//包里匹配
            extend[i]=now;
            p=i;//更新
        }

 这就是拓展kmp的大致流程,接下来就整一个洛谷的模板题,把代码贴上去:

https://www.luogu.com.cn/problem/P5410

#include<iostream>
#include<cstring>
#define ll long long
using namespace std;
char str[20000007],str1[20000007];
//str为文本串,str1是模式串
ll nex[20000007],extend[20000007],len,len1;
void getnex()//对模式串进行匹配
{
    nex[0]=len1;
    int now=0;
    while(str1[now]==str1[now+1]&&now+1<len1)now++;
    nex[1]=now;
    int p=1;
    for(int i=2;i<len1;i++)
    {
        if(i+nex[i-p]<p+nex[p])nex[i]=nex[i-p];
        else
        {
            now=p+nex[p]-i;
            now=max(now,0);
            while(str1[now+i]==str1[now]&&now+i<len1)
            {
                now++;
            }
            nex[i]=now;
            p=i;
        }
    }
    return ;
}
void getextend()//模式串与文本串匹配
{
    getnex();
    int now=0;
    while(str1[now]==str[now]&&now<len1&&now<len)now++;
    extend[0]=now;
    int p=0;
    for(int i=1;i<len;i++)
    {
        if(i+nex[i-p]<p+extend[p])extend[i]=nex[i-p];
        else
        {
            now=p+extend[p]-i;
            now=max(now,0);
            while(str[now+i]==str1[now]&&now+i<len&&now<len1)
            {
                now++;
            }
            extend[i]=now;
            p=i;
        }
    }
    return ;
}
int main()
{
    scanf("%s%s",str,str1);
    len=strlen(str);
    len1=strlen(str1);
    ll z=0,p=0;
    getextend();
    for(int i=0;i<len1;i++)
    {
        z^=1ll*(i+1)*(nex[i]+1);
    }
    for(int i=0;i<len;i++)
    {
        p^=1ll*(i+1)*(extend[i]+1);
    }
    printf("%lld\n%lld\n",z,p);
    return 0;
}

其实拓展kmp思想也是从kmp而来,所以学好kmp很重要!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值