注意:本文面向对KMP有了解但对next数组不大清楚的人!!萌新请先了解基础!!

我自己看蓝书(刘汝佳的训练指南)学了下KMP,然而蓝书也没有细讲,只是让我们查百度并且贴上了代码(其实应该是可以理解的但是我太菜了),找百度看了四个博客也并没有看太懂,但是借鉴了一点思想。
所以就自己对着代码想nex的意义,然后花了2个小时码出来这个。。。代码里的注释是我为了理解(推导)而敲出来的如果看不懂可以私信我。

因为我刚学KMP的时候感受到了绝望。。。所以希望能对同样遇到困难得人有所帮助。

ps:注释中的\((a...b)=(c...d)\)表示

\(T[a] = P[c],\)

\(T[a+1] = P[c+1],\)

\(.\)

\(.\)

\(.\)

\(T[b-1] = P[d-1],\)

\(T[b] = P[d].\)

(在\(getfail\)中T就是P)

#include <iostream>
#include <cstdio>
#include <cstring>
//其实只是为了用strlen而已
using namespace std;
#define reg register
const int N = 1e6+5, INF = 1e9;

char T[N], P[N];
int lt, lp;
int nex[N];  // nex[0]=0
             // nex[1]=x1 -> 0...x1-1=1-x1...1-1 (其实就是前缀与后缀,看了几个人的博客都有提到这个概念)
             // nex[2]=x2 -> 0...x2-1=2-x2...2-1
             // .
             // .
             // .
             // nex[n]=xn -> 0...xn-1=n-xn...n-1(@0) =>
             // if n,i faild -> compare xn,i   (@1) 在这里显然@0与@1互为充要条件(似乎表述有问题??算了那是数学上的事情。就是说两个是可以互推的)

inline void getfail() { //其实就是用模板匹配模板
    int j = 0;    //j -> 模板的下标   i -> 待匹配字串的下标(这里就是模板)    (@2)
    for (reg int i = 1; i < lp; ++i) {
        // j=0 -> compare P[i], P[0] -> 从头开始  =>                   =>//
        // 1.P[i]!=P[0] -> compare P[++i], P[0] -> (j=0)not change     =>//
        // 2.P[i]==P[0] -> compare P[++i], P[1(++j)] -> ++j            =>//
        //                                                             =>//
        // j>0 -> compare P[i], P[j] -> {*}={0...j-1==i-j...i-1}  =>   =>//
        /* 1.P[i]!=P[j] -> (@1) (@2) ->            =>//                =>//
             compare nex[j],i =>                   =>//                =>//
             (1).P[nex[j]]!=P[i] -> (@1) (@2) ->   =>//                =>//
                 compare nex[nex[j]],i             =>//  j=nex[j]      =>//
                 .                                 =>//                =>//
                 .                                 =>//                =>//
                 .                                 =>//                =>//
            2.P[i]==P[j] -> compare i+1,j+1 => ++j                     =>//
        */
        while (j && P[i] != P[j]) j = nex[j];
        // 1.P[i]==P[j] => (though j==0)  (这里可以推出两点)
        //   (1)0...j=i-j...i -> (@0) -> nex[i+1]=j+1
        //   (2)compare ++j,++i -> ++j
        //   => ++j, nex[i+1]=j (因为for循环中有++i)
        // 2.P[i]!=P[j] -> j==0 (注意while里的条件) -> when i+1!=x 从头开始
        //   (因为P[i]与P[0]也不相等所以如果i+1失配了那么就只能从模板的第一个开始匹配) =>
        //   compare i+1,0 -> (@0) -> nex[i+1]=0 (j)
        if (P[i]==P[j]) ++j;
        nex[i+1] = j;
    }
}

inline void KMP() {
    getfail();
    int j = 0;
    for (reg int i = 0; i < lt; ++i) {
        // most same as getfail()
        while (j && T[i] != P[j]) j = nex[j];
        // 1.j==lp-1 -> success -> print(i-lp+1) 匹配成功
        // 2.T[i]==P[j] -> compare i+1,++j  如果相等就应该继续比较P[i+1]与P[j]
        // 3.T[i]!=P[j] -> j=0 -> (@0) -> compare i+1, 0(就是j)
        if (T[i] == P[j]) ++j;
        if (j == lp) printf("%d\n", i-lp+1+1);
    }
}

int main() {
    scanf("%s %s", T, P);
    lt = strlen(T), lp = strlen(P);
    KMP();
    for (reg int i = 1; i <= lp; ++i) { //洛谷P3375 要求输出但却没有规定next数组,毕竟每个人的模板不同,在观察之下我发现似乎就是这样结果AC了。。。
        printf("%d ", nex[i]);          //这在KMP里不是重点啦所以不用纠结这一块QWQ
    }                                   //似乎是有大佬用hash做出来了%%%
    putchar('\n');
    return 0;
}

其实这似乎是MP,KMP还需要对nex数组进行优化(训练指南P213),但是MP已经到达了时间复杂度的下限,并且已经足以使用,所以有想法的同学就百度呗。

事实上,我认为这种学习的方式也是可以尝试的,尽管可能效率较低(我会这么做的根本原因可能是初中主要在搞数竞习惯了自己推导的方法)

如果没有我的耳机,在机房嘈杂的环境下我根本不可能静下心来去思考QWQ。


\[ Upd \ in \ 2019.10.10 \]

讲下优化,顺便把博客搬过来。

其实就是如果 $ nxt $ 与自己相同,那么失配的时候我们就已经知道失配了,所以可以跳过去,但是注意有些题目里不能直接跳掉,比如统计某些方案数的时候。

所以代码就是

if (P[now] == P[i + 1]) nxt[i + 1] = nxt[now];
else nxt[i + 1] = now;

还有对于最后一项(最后一个点的后面,即成功匹配的时候),他的 $ nxt $ 是不能优化的,虽然判断的条件恒不成立,但是在多组数据中,不清空可能会导致条件成立,结果就是 $ nxt[lp] $ 被优化了。这是要注意的点。

01-19 14:10