POJ 3461 Oulipo(KMP模板)


给定一个模板串和一个文本串,求模板串在文本串中出现了多少次(可以重叠)。


匹配成功后不跳出,认为模板串结尾的最后一个字符失配再继续跳到前面去匹配。


KMP算法简介:


KMP 适用于单串匹配(因为只有一个next数组,只能记录一个串的信息,如果多个串匹配的话,显然一个next是不能满足的,得建一棵字典树,那就变成AC自动机了^ ^)。KMP可以高效(复杂度为o(n),n为文本串的长度)查出模板串在文本串在位置,不过要先预处理o(m)(m为模板串的长度)next数组。


私以为KMP的精华在于next数组,next的数组有什么用呢?


当和模板串匹配到第i个字符时,如果文本串的当前字符和这个第i个字符不匹配的时候(说明前i-1个字符匹配成功了),为了减少多余的匹配,应该和模板串再继续匹配的位置。


比如模板串abaabc,当比配到abaabc时,发现和最后的'c'并不匹配,如果又从头匹配,则忽略了一个信息,就是文本串和模板的有一部分是已经匹配好的,就是abaab部分。


可以发现,在这个串中,abaabc的第四、五位恰好同第一、二位是同样的,那么我们可以把模板串往后挪三位,把文本串中失配的字符直接和第二个'a'匹配,因为可以知道前面的ab是一定可以匹配的。


所以next[5] = 2,代表字符5失配以后,为了减少匹配,文本串要和模板串匹配的下一个位置应该是2(数组下标从0开始)。


next数组可以使得匹配过程中,文本串不挪动,而挪动模板串来进行匹配(复杂度为o(n)的原因)。


怎么求得next数组呢?


首先设置初始条件,next[0] = -1;

这里先约定好,若文本串第i个字符和模板串的第k个字符失配后,查询发现模板串的next[k]是-1,那么表示前面的匹配全部作废,下一次和文本串第i+1个字符匹配时,要跳到整个模板串的开头,从头匹配。


然后next数组的求法,其实是模板串自己和自己匹配。假设模板串自己和自己已经匹配到第i位和第k位(k的初始值为-1),查询后会有三种结果:

1.k是-1,那么从第i+1位起要和模板串从头匹配了;

2.第i位和第k位不同,那么要挪动模板串,第i位要和next[k]位比较;

3.第i位和第k位相同,那么我们可以计算next[i+1]了,但计算next[i+1]还是有个小优化的。


如果单纯地把next[i+1]指向next[k+1],的确是可行的,因为前i个字符的某个前缀正好是前k个字符。但是如果第i+1位和第k+1位相同时,我们可以想象到这种情况:匹配时和第i+1个字符失配,然后跳去next[i+1](= k+1),但是i+1和k+1是一样的字符,这不是显然的又会失配吗?所以又会跳去next[k+1]。


既然如此,那么不如我们自己在建立next函数的时候,直接压缩路径好了:next[i+1] = next[k+1]。

因为每次都不允许单纯指向,所以最后的求好的next数组在实际使用过程中,一定不会出现上面那种多余匹配的情况。


这样就是next数组的全部了。


然后实际匹配的时候,基本和next函数内部实现一样的,不过求next数组本身也是一个模板串自己和自己匹配的过程。


#include <algorithm>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
using namespace std;

void Getnextval(char p[], int next[])
{
    int i = 0, k = -1;
    next[0] = -1;
    while(p[i])
    {
        if(k == -1 || p[i] == p[k])
        {
            i++, k++;
            if(p[i] == p[k]) next[i] = next[k];
            else next[i] = k;
        }
        else k = next[k];
    }
}

int next[10100];
void kmp(char w[], char t[])
{
    int ans = 0;
    int i = 0, j = 0;
    while(t[i])
    {
        if(j == -1 || t[i] == w[j])
        {
            i++, j++;
            if(w[j] == 0) ans++, j = next[j];
        }
        else j = next[j];
    }
    printf("%d\n", ans);
}

char t[1000100], w[10100];
int main()
{
//    freopen("3461.in", "r", stdin);
    int cas;
    scanf("%d", &cas);
    while(cas--)
    {
        scanf("%s%s", &w, &t);
        Getnextval(w, next);
        kmp(w, t);
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值