kmp/exkmp(模板)

强大的博友 https://blog.youkuaiyun.com/starstar1992/article/details/54913261
(要入门看上面的文章,里面对于求next数组的原理解释的太好了!)
这里在加一个例题(算是裸kmp吧)
对于kmp的一些理解,我写在代码里了

题目链接poj3461 Oulipo

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>

using namespace std;

const int maxn = 1e4 + 5;
const int maxl = 1000000 + 5;

int nex[maxn];
char p[maxl],s[maxl];

void cal_next(int &lenp)
{
    nex[0] = -1;
    int j = -1;
    for(int q = 1; q < lenp; q++){
        //循环内:
        //向前找已经子串的最大相同前后缀的前缀的最后一个字符往后一个字符
        //如果恰好等于最后新加的字符,那么这个最大前相同后缀子串的数量就是上一个
        //的数量+1,如果没找到,那么while循环退出的条件就是 j = -1,nex[q]就为-1表示该位置的
        //子串无最大相同前后缀子串
        while(j > -1&&p[j + 1] != p[q]){
            j = nex[j];
        }
        if(p[j + 1] == p[q]){//因为到这一步,可能是上一步没找到符合要求的位置,也可能是找到了要进行
            j += 1;      //处理或者是,
        }               //所以为了保险,就设置了这个判断,反正这个串没有相同的前后子串,这里也不会+1;
        nex[q] = j;
    }
}

int kmp()
{
    int lenp = strlen(p);
    int lens = strlen(s);
    cal_next(lenp);
    int ans = 0;
    int k  = -1;
    for(int i = 0; i < lens; i++){
        //ptr和str不匹配,且k>-1(表示ptr和str有部分匹配)

        /*这里的循环是对kmp进行的优化,算是吧。。如果找到当前子串的最大相同前缀后缀
        数,但是将模式串移动到此位置时,如果,它的后一位还是不等于文本串的
        当前位置,就没有必要移动这一次 例如:
        ababc去匹配ababdababc
        当第一次将p串最后一位和ababdababc中的d比较不匹配时,将p串移动c前面的子串
        的最大相同前后缀和后,p串中的 "aba" 和 s串中的 "abd"比较时,还是不匹配,所以还是
        要进行移动,所以这个循环是将多次无效移动进行了合并
        */
        while(k > -1&&p[k + 1] != s[i]){
            k = nex[k];//向前回溯,找到符合条件前缀子串位置,或者到-1退出循环,                                    
        }              //从第一个字符再进行匹配
        if(p[k + 1] == s[i]){
            k++;
        }   
        //说明完全匹配
        if(k == lenp - 1){
            ans++;
            //cout<<i - p.size() + 1<<endl;
             //因为有可能两个串有重叠的情况,所以这里要处理一下,跟匹配失败类似
            k = nex[k];
        }
    }
    return ans;
}

int main()
{
    int t;
    scanf("%d",&t);
    while(t--){
        scanf("%s%s",p,s);
        int ans = kmp();
        printf("%d\n",ans);
    }
    return 0;
}

关于next数组在求最小循环节中的性质传送门

扩展kmp
参考博客

hdu 6153

模板

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <string>
#include <stack>
#include <queue>
#include <map>
#include <vector>

using namespace std;

typedef long long ll;

const int MAXN = 1e6 + 7;
const int Mod = 1e9 + 7;

//拓展kmp
char s[MAXN] , t[MAXN];
int Next[MAXN] , extend[MAXN];

void GetNext(int lent)
{   
    Next[0] = lent;
    int a = 0 , p = 0;
    for(int i = 1; i < lent; i++){
        //两种情况,i 的位置大于p或者ℹ + next[i - a] >= p
        if(i >= p || i  + Next[i - a] >= p){
            //第一种情况,就直接把p放到 i上 然后暴力找
            if(i >= p){
                p = i;
            }
            //为什么不写if里,因为如果是第二种情况,由于 t[p] != t[p - a] ,并且t[p -a ] == t[p - i]
            //则t[p] 一定 != t[p -1];
            while(p < lent && t[p] == t[p - i]){
                p++;
            }  
            Next[i] = p - i;
            a  = i;//更新a的位置
       }
        else{
            Next[i] = Next[i - a];
        }
    }
} 

//求extend和上面求next一样,只不过把t和自己匹配换成了,s和t'匹配
void exkmp(int lens , int lent)
{
    int a = 0, p = 0;
    GetNext(lent);
    for(int i = 0; i < lens; i++){
        if(i >= p || i + Next[i - a] >=  p){
            if(i >= p){
                 p =  i;
            }
            while(p < lens && p - i < lent && s[p] == t[p - i]){
                p++;
            }  
            extend[i] = p -  i;
            a = i;
        }
        else{
            extend[i] = Next[i - a];
        }
    } 
}

int main()
{
    int n;
    scanf("%d",&n);
    while(n--){
        scanf("%s%s",s,t);
        int lens = strlen(s);
        int lent = strlen(t);
        //cout<<lens<<' '<<lent<<endl;
        reverse(s , s + lens);
        reverse(t , t + lent);
        exkmp(lens, lent);
        ll ans = 0;
        for(int i  = 0; i < lens; i++){
            if(extend[i]){
                ll temp = extend[i];
                ans = (ans % Mod + (temp % Mod) * ( (temp + 1) % Mod ) / 2) % Mod;
           }
        }   
        printf("%d\n", ans);
    }
    return 0;
}
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值