【字符串】拓展KMP

【求模式串与主串的每一个后缀的最长公共前缀】

有两个字符串 主串S 与 模式串T

求主串S从第 i 个位置开始,与T的最长相同前缀的长度

即 S[ i ]~S[ i+mlen ] = T[ 0 ]~T[ mlen ] 的 mlen 值

对于 i∈[ 0,Slen ) 的任意一个i都求一遍

 

定义:(这里的Next与KMP的Next数组意义不同)

 

Next数组储存模式串T的第 i 个位置开始与自己(T)的最长相同前缀的长度

 

Extend数组储存主串S的第 i 个位置开始与模式串T的最长相同前缀的长度

首先假设 a 和 p 两个遍历,表示S串从 a 位置开始到 p-1 位置结束,与T串从 0 开始到 p-a 位置结束,这两段子串相匹配

那么,如果现在处理到了第i个位置

求的Extend[ i ]是S串第i个位置开始与模式串T的最长相同前缀的长度

从这里开始分下面三种情况考虑

讨论 i+Next[i-a] 与 p 的关系

①    如果 i+Next[i-a]<p,即此时三段字符串相同的情况如下

  显然,此时满足 Extend[ i ] = Next[ i-a ]

②    如果 i+Next[i-a]==p

  由刚才的定义可知 S[p]!=T[p-a] ,且满足该等式时说明 T[p-a]!=T[p-i]

  但是 S[p] 与 T[p-i] 的关系未知(如果相等,说明 Extend[i] 还能增大)

  所以此时就可以直接从 S[p] 和 T[p-i] 开始向后继续匹配(提高速度)

③    如果i+Next[i-a]>p

  因为 S[p]!=T[p-a] ,但是 T[p-a]==T[p-i] ,所以 S[p]!=T[p-i] 成立,所以 Next[i] 的值无意义,Extend[i] 的值就可以直接等于 p-i

以上三种情况考虑完后就可以求出 Extend 数组了

对于 Next 数组,因为求的是从 i 位置开始与自己的最长相同前缀的长度

所以可以直接根据上述求Extend数组的思想求出Next数

完整程序如下:

#include<bits/stdc++.h>
using namespace std;
string S,T;
int Slen,Tlen,Next[1000050],Extend[1000050];

void getNext()
{
    int i,a=0,p=0;
    Next[0]=Tlen;//特殊预处理
    for(i=1;i<Tlen;i++)
    {
        if(i>=p||i+Next[i-a]>=p)//如果i比目前处理到的p的值要大(此时要右移模式串然后重新寻找匹配的p,KMP),或者出现了第2、3种情况
        {
            if(i>p)
                p=i;//如果i比p大,可以直接更新p
            while(p<Tlen&&T[p]==T[p-i])//尝试向右更新p值
                p++;
            Next[i]=p-i;
            a=i;//右移模式串头部
        }
        else//第1种情况,直接拿i-a位置的值当答案
            Next[i]=Next[i-a];
    }
}// Next[i] 表示 T 串内从 i 位置开始与 T 串前缀相同的最长长度

void getExtend()//注释同上
{
    int i,a=0,p=0;
    for(i=0;i<Slen;i++)
    {
        if(i>=p||i+Next[i-a]>=p)
        {
            if(i>p)
                p=i;
            while(p<Slen&&p-i<Tlen&&S[p]==T[p-i])
                p++;
            Extend[i]=p-i;
            a=i;
        }
        else
            Extend[i]=Next[i-a];
    }
}// Extend[i] 表示 S 串内从 i 位置开始与 T 串前缀相同的最长长度

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    
    cin>>S>>T;
    Slen=S.size();
    Tlen=T.size();
    getNext();
    getExtend();
    
    
    return 0;
}

本文参考了https://subetter.com/algorithm/extended-kmp-algorithm.html,原文写得很好,图片也引用自原文

例题 HDU 6629

求的是按照最笨的办法去求字符串S除自己以外的每一个后缀与自己的最长公共前缀长度总和

过程中进行两字符对比的次数

只要把S也当作模式串T直接代入拓展KMP

然后求除了 Extend[0] 外的所有Extend之和

每次还要判断,如果最长公共前缀没有到达S串最后的位置的话,按照题意的办法还要多判断一次,注意此时的答案+1

原文地址:https://www.cnblogs.com/stelayuri/p/12506776.html