[学习笔记]扩展KMP

〇、前言 ¶

扩展 \(\rm kmp\) 主要的功能是:对于串 \(S,T\),记 \(ext_i=\text{LCP}(S[i:],T)\),它可以在线性复杂度内求得所有 \(ext_i(i\in [1,|S|])\).

不难看出,若某个 \(ext_i=|T|\),那么说明 \(T=S[i:i+|T|-1]\),事实上,这就是 \(S,T\) 的一个匹配位置。所以说,它叫做扩展 \(\rm KMP\). 并且,其思想是基于 \(\rm KMP\) 的,故而,\(\rm KMP\) 是必要的前置知识。

注意:下文所有的字符串的首下标都是 \(1\) 而非 \(0\).

壹、算法过程 ¶

事实上,扩展 \(\rm KMP\) 也是利用了 \(\rm KMP\) 的 “利用已经之前部分匹配的有效信息,保持主串指针不回溯,通过修改指针,让模式串尽量地移动到有效的位置。”

\(\rm exkmp\) 主要利用两个数组:\(ext_i,Z_i\)\(ext_i\) 的定义上文给出,对于 \(Z_i\),有

\[Z_i=\text{LCM}(T,T[i:]) \]

即模式串的每个后缀与模式串本身的匹配,不难看出,\(Z_i\) 的本质还是 \(ext_i\),所以我们只需要搞清楚如何求解 \(ext_i\),即可解决 \(Z_i\).

进入算法过程,假设我们当前要求得 \(ext_i\),已经算出 \(ext_1,ext_2,\cdots ext_{i-1}\),并且有数对 \((p,r)\),其中 \(p\) 是使得 \(p+ext_p-1\) 最大的 \(p\),并且记 \(r=p+ext_p-1\)\(r\) 的含义即当前最长的 \(S[p:],T\) 的匹配的右边界. 我们假设已知 \(Z\) 数组。

由定义,我们已知 \(S[p:r]=T[1:r-p+1]\),从而,有 \(S[i:r]=T[i-p+1:r-p+1]\),所以,若我们要匹配 \(S[i:]\)\(T\),我们可以先匹配 \(S[i:r],T\),即先匹配 \(T[i-p+1,r-p+1]\)\(T\),针对这个匹配,我们就可以使用 \(Z\) 数组了。

\(len=Z_{i-p+1}\),那么,分两种情况:

  • \(i-p+1+len-1<r-p+1\Rightarrow i+len-1<r\),则说明 \(T[i-p+1:r-p+1]\)\(T\) 存在某个字符不一样,那么一定有 \(ext_i=len\)
  • \(i-p+1+len-1\ge r-p+1\Rightarrow i+len-1\ge r\),那么可以说明 \(ext_i=len\) 吗?不行,因为我们目前只知道的信息是,\(T[i-p+1:r-p+1]\)\(S[i:]\) 的一个前缀,同时 \(T[i-p+1:r-p+1]\) 也是 \(T\) 的一个前缀,对于 \(T\) 超过 \(r-p+1\) 的部分与 \(S\) 的匹配我们是尚不知晓的,换句话说,我们只知道 \(ext_i\) 至少为 \(len\),至于它到底有多大,我们得将 \(S[r+1:]\)\(T[r-p+1+1:]\) 进行暴力匹配(\(S[r]=T[r-p+1]\) 我们也是知道的),并且此时我们可以更新 \((p,r)\)

至于如何计算 \(Z\),由于我们计算 \(Z_i\)\(Z_1,Z_2,\cdots Z_{i-1}\) 都是知道的,所以也可以直接使用上述过程,它是天然自洽的。

特别地,若 \(i>r\),那么我们直接暴力匹配,得到新的 \(r,p\) 即可,不过需要注意的是,如果一开始就失配,需要特判,当然也有可能是我打法问题 🙃.

另外,我尝试在代码实现中以全闭区间为下标,这样确实更容易理解......不过,代码实现难上加难了......以后要打这个东西,还是下标从 \(0\) 开始,打左闭右开区间算了......

叁、参考代码 ¶

#include<cstdio>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<vector>
using namespace std;

// #define NDEBUG
#include<cassert>

namespace Elaina{
    #define rep(i, l, r) for(int i=(l), i##_end_=(r); i<=i##_end_; ++i)
    #define drep(i, l, r) for(int i=(l), i##_end_=(r); i>=i##_end_; --i)
    #define fi first
    #define se second
    #define mp(a, b) make_pair(a, b)
    #define Endl putchar('\n')
    #define mmset(a, b) memset(a, b, sizeof a)
    // #define int long long
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int, int> pii;
    typedef pair<ll, ll> pll;
    template<class T>inline T fab(T x){ return x<0? -x: x; }
    template<class T>inline void getmin(T& x, const T rhs){ x=min(x, rhs); }
    template<class T>inline void getmax(T& x, const T rhs){ x=max(x, rhs); }
    template<class T>inline T readin(T x){
        x=0; int f=0; char c;
        while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
        for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
        return f? -x: x;
    }
    template<class T>inline void writc(T x, char s='\n'){
        static int fwri_sta[1005], fwri_ed=0;
        if(x<0) putchar('-'), x=-x;
        do fwri_sta[++fwri_ed]=x%10, x/=10; while(x);
        while(putchar(fwri_sta[fwri_ed--]^48), fwri_ed);
        putchar(s);
    }
}
using namespace Elaina;

const int maxn=2e7;

char a[maxn+5], b[maxn+5];
int z[maxn+5], ext[maxn+5], n, m;

inline void getz(){
    int p=0; /** the pos where we have the maximum @p r */
    int r=0; /** the maximum @p i+z[i]-1 */
    z[1]=m;  /** special treatment */
    /**
     * @warning if @p p==1 , then @p i-p+1==i and we'll invoke @p z[i] , but which hasn't updated yet, so we make @p p==2 initially
    */
    rep(i, 2, m){ /** pay attention to the range */
        if(i>r || i+z[i-p+1]-1>=r){
            if(i>r){
                if(b[i]==b[1]) p=r=i;
                else{ /** if it failed in the first position */
                    /** @p r must be set as i-1 to ensure the running time */
                    p=i, r=i-1, z[i]=0;
                    continue;
                }
            }
            /** compare @p b[r+1] and @p b[r+1-i+1] */
            while(r<m && b[r+1]==b[r+1-i+1]) ++r;
            z[i]=r-i+1, p=i; /** pay attention to update @p p */
        }
        else z[i]=z[i-p+1];
    }
}

inline void getext(){
    int p=0, r=0;
    rep(i, 1, n){
        // printf("When i == %d, p == %d, r == %d\n", i, p, r);
        // printf("%d + z[%d] - 1 == %d\n", i, i-p+1, i+z[i-p+1]-1);
        if(i>r || i+z[i-p+1]-1>=r){
            if(i>r){
                if(a[i]==b[1]) p=r=i;
                else{ /** @brief failed in the first comparison */
                    /** @p r must be set as i-1 */
                    p=i, r=i-1, ext[i]=0;
                    continue;
                }
            }
            /** @brief compare @p a[r+1] and @p b[r+1-i+1] */
            while(r<n && r-i+1<m && a[r+1]==b[r+1-i+1]) ++r;
            ext[i]=r-i+1, p=i;
        }
        else ext[i]=z[i-p+1];
    }
}

signed main(){
    scanf("%s", a+1), scanf("%s", b+1);
    n=strlen(a+1), m=strlen(b+1);
    getz(); getext();
    ll ans=0;
    rep(i, 1, m) ans^=1ll*i*(z[i]+1);
    writc(ans);
    ans=0;
    rep(i, 1, n) ans^=1ll*i*(ext[i]+1);
    writc(ans);
    return 0;
}
posted @ 2021-06-22 21:26  Arextre  阅读(82)  评论(0编辑  收藏  举报