扩展KMP的应用

扩展KMP的应用

扩展kmp在字符串匹配专题的应用很广,但很多时候也还是能用kmp、马拉车等字符串匹配算法代替,这篇文章是笔者为18年湖南省赛准备的复习文,文章整理了笔者遇到过的扩展kmp在一些方面的应用

  • 题目中出现的字符串名称及其定义:
    • 循环同构串: abcd->bcda->cdab->dabc ,将首字符逐一左移至字符串尾部,得到
      的所有字符串。
    • 后缀子串:Si表示以第i个字符为首的S的后缀字符串。
    • 重复不重叠字符串:abcabc,abc重复了两次。书面表示为AAA (A表示字符串)
  • 应用分类:
    • 定义题:裸题(求T与S的所有后缀子串的最长前缀公共子串长度)
      FZU-1901
      ZOJ 3587
    • 回文前缀子串和回文后缀子串上的应用
      HDU 3613
    • 分治 + ex_kmp
      HUNNU OJ 11565 可惜标程错了

一句总结: 大部分应用都是将自己与反串匹配,就能处理题目要求的字符串性质

一、定义题

FZU-1901
题意:字符串S,问满足S[i] = S[i+P],(i ∈[0,Size -P-1]),升序顺序输出P的取值。


很裸的ex_kmp的定义题!判断Next[j] == Size - j +1 ,以S[j]开头后缀子串是S的一个前缀子串。

/*********************************
*** FZU 1901 ex_kmp 的 Next数组
*********************************/

#include <iostream>
#include <fstream>
#include <stdio.h>
#include <algorithm>
#include <math.h>
#include <map>
#include <string.h>
#include <time.h>
#include <stdlib.h>
#define llt long long

using namespace std;

const int N = 1e6+7777;
int Next[N];
char s[N];
void get_Next(int n){
    Next[0] = n;//以s[0]开头后缀子串显然是本串
    int i=0;
    while(i+1<n&&s[i]==s[i+1])++i;//前面有i+1个数相同
    Next[1] = i;
    int po = 1;// 到达最远比较位置
    for(int i=2;i<n;++i){
        if(i+Next[i-po]<Next[po]+po)//不能等于
            Next[i] = Next[i-po];
        else{
            //if(i==9) cout<<i<<endl;
            int j = max(0,Next[po]+po-i);
            while(i+j<n&&s[j]==s[i+j])++j;
            Next[i] = j;
            po = i;
        }
    }
    Next[n] = 0;
}

int ans[N];
int main(){
    int T;
    scanf("%d",&T);
    int cas = 0;
    while(T--){
        scanf("%s",s);
        int n = strlen(s);
        get_Next(n);
        //cout<<Next[9]<<endl;
        int cnt = 0;
        for(int i=1;i<=n;++i)
            if(n-i==Next[i]) ans[++cnt] = i;
        printf("Case #%d: %d\n",++cas,cnt);
        for(int i=1;i<=cnt;++i)
            printf("%d%c",ans[i]," \n"[i==cnt]);

    }
    return 0;
}

zoj 3587
题意:求S的两个子串拼接成T的方案数。


ex_kmp 统计S的子串与T的前后缀长度为k的子串数。

/*************************************************
*** zoj 3587  拓展kmp的定义题:求出S的后缀子串i
                  与T的最长前缀k,实际上表示已si
                  开头的子串段长度<=k,均有一种方案!
              同理,找到S反串与T的反串的最长前缀!

*** 实际运用ex_kmp求出与T的前缀长度为k的子串的个数!
*************************************************/
#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <math.h>
#define llt long long
#include <map>
#define fi first
#define se second
#define lson (rt<<1)
#define rson ((rt<<1)+1)
using namespace std;

const int N = 1e5+777;
char s[N],t[N];
char ss[N],tt[N];//反串
llt sum[N],a[N];//a 记录 S的子串是T的长度为k的后缀子串的个数
                // sum 记录 S的子串是T的长度不超过k的后缀子串的个数和
int Next[N],extend[N];
void getNext(char S[],int n){
    Next[0] = n;
    int i =0 ;
    while(i+1<n&&S[i]==S[i+1]) ++i;
    Next[1] = i;
    int po = 1;
    for(int i=2;i<n;++i){
        if(i+Next[i-po]<po+Next[po])
           Next[i] = Next[i-po];
        else {
            int j = max(0,po+Next[po]-i);
            while(i+j<n&&S[j]==S[i+j]) ++j;
            Next[i] = j;
            po = i;
        }
    }
}

void getExtend(char S[],char T[],int slen,int tlen){
    getNext(T,tlen);
    int i=0;
    int mixn = min(slen,tlen);
    while(i<mixn&&S[i]==T[i]) ++i;
    extend[0] = i;
    int po = 0;
    for(int i=1;i<slen;++i){
        if(i+Next[i-po]<po+extend[po])
            extend[i] = Next[i-po];
        else {
            int j = max(0,po+extend[po]-i);
            while(j<tlen&&i+j<slen&&T[j]==S[i+j]) ++j;
            extend[i] = j;
            po = i;
        }
    }
}
int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        scanf("%s",s);
        scanf("%s",t);
        int slen = strlen(s);
        int tlen = strlen(t);
        for(int i=0;i<slen;++i) ss[slen-1-i] = s[i];
        for(int i=0;i<tlen;++i) tt[tlen-1-i] = t[i];
        /*统计S的子串为T的后缀子串的长度为k的个数*/
        memset(a,0,sizeof a);
        getExtend(ss,tt,slen,tlen);
        for
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值