zoukankan      html  css  js  c++  java
  • 扩展KMP模板(学习)

    学习链接:https://www.luogu.org/problemnew/solution/P5410

    一、引言

    一个算是冷门的算法(在竞赛上),不过其算法思想值得深究。

    二、前置知识

    1. kmp的算法思想,具体可以参考这篇日报

    2. trie树(字典树)。

    三、经典扩展kmp模板问题:

    扩展kmp的模板问题:

    给你两个字符串s,t,长度分别为n,m。

    请输出s的每一个后缀与t的最长公共前缀。

    哈希是不可能的,这辈子都不可能的。

    mathcal{AC}AC自动机?好像更不可做了。

    我们先定义一个:extend[i]extend[i]表示S[i...n]S[i...n]与TT的最长公共前缀长度,而题意就是让你求所有的extend[i]extend[i]。

    注:以下字符串均从1开始计位。

    例子:

    如果S=aaaaaaaaaabaaS=aaaaaaaaaabaa,n=13n=13

    T=aaaaaaaaaaaT=aaaaaaaaaaa,m=11m=11

    由图可知,extend[1]=10extend[1]=10、extend[2]=9extend[2]=9。

    我们会发现:在求extend[2]extend[2]时,我们耗费了很多时间,但我们可以利用extend[1]extend[1]来更快速地求解:

    因为已经计算出extend[1]=10extend[1]=10。

    所以有:S[1...10]=T[1...10]S[1...10]=T[1...10]

    然后得:S[2...10]=T[2...10]S[2...10]=T[2...10]

    因为计算extend[2]extend[2]时,实际上是S[2...n]S[2...n]和TT的匹配,

    又因为刚刚求出了S[2...10]=T[2...10]S[2...10]=T[2...10],

    所以匹配的开头阶段是求T[2...10]T[2...10]与TT的匹配。

    这时我们可以设置辅助参数:nextnext,next[i]next[i]表示T[i,m]T[i,m]与TT的最长公共前缀长度。

    那么对于上述的例子:next[2]=10next[2]=10

    即:T[2...11]=T[1...10]T[2...11]=T[1...10]

    然后得:T[2...10]=T[1...9]T[2...10]=T[1...9]

    ∴S[2...10]=T[2...10]=T[1...9]S[2...10]=T[2...10]=T[1...9]

    也就是说求extend[2]extend[2]的匹配的前9位已经匹配成功了,不用再匹配一遍了,可以直接从S[11]S[11]和T[10]T[10]开始匹配,这样我们就省下了很多时间。

    这其实就是kmp的思想。

    对于一般情况:

    extend[1...k]extend[1...k]已经算好,并且在以前的匹配过程中在S串中的最远位置是pp,即p=max(i+extend[i]-1)p=max(i+extend[i]1),其中i=1...ki=1...k。

    然后我们设取这个最大值k的位置是p0p0。

    首先,根据定义,S[p0...p]=T[1...p-p0+1]S[p0...p]=T[1...pp0+1]。

    我们设T[k-p0+1]T[kp0+1]在TT串中对应的位置为aa,T[k-p0+2]T[kp0+2]在TT串中所对应的位置为bb。(仅仅是为了下面的讲解方便)

    然后令L=next[b]L=next[b]。

    下面分两种情况讨论:

    第一种情况:k+L<pk+L<p

    也就是S[k+L]S[k+L]这个位置在pp前面,如图:

    我们设l1=1l1=1,r1=Lr1=L,l2=bl2=b,r2=b+L-1r2=b+L1。(bb的定义在上一张图)

    此时l1l1、r1r1、l2l2、r2r2的位置如图所示。

    也就是说,T[l1...r1]=T[l2...r2]T[l1...r1]=T[l2...r2]。

    color{red}{ ext{红线}}红线与color{green}{ ext{绿线}}绿线与color{blue}{ ext{蓝线}}蓝线相等。

    然后由nextnext的定义可知,T[r1+1]!=T[r2+1]T[r1+1]!=T[r2+1]。

    又因为T[r2+1]=S[k+L+1]T[r2+1]=S[k+L+1]

    所以T[r1+1]!=S[k+L+1]T[r1+1]!=S[k+L+1],这两个字符不一样。

    又因为color{red}{ ext{红线}}红线与color{blue}{ ext{蓝线}}蓝线相等,这两条线已经匹配成功。

    所以extend[k+1]=Lextend[k+1]=L,也就是next[b]next[b]。

    所以这段的代码比较简单:

    if(i+nxt[i-p0]<extend[p0]+p0)extend[i]=nxt[i-p0];
    //i相当于k+1
    //nxt[i-p0]相当于L
    //extend[p0]+p0相当于p
    //因为在代码里我是从0开始记字符串的,所以本应在小于号左侧减1,现在不用了。

    第二种情况:k+L>=pk+L>=p

    也就是S[k+L]S[k+L]这个位置在p前面,如图:

    图可能略丑

    同样,我们设l1=1l1=1,r1=Lr1=L,l2=bl2=b,r2=b+L-1r2=b+L1。

    此时l1l1、r1r1、l2l2、r2r2的位置如图所示。(r1r1的位置可能在p-p0+1pp0+1前或后)

    同理,color{red}{ ext{红线}}红线与color{green}{ ext{绿线}}绿线与color{blue}{ ext{蓝线}}蓝线相等。

    那么我们设(k+L)(k+L)到pp的这段距离为xx。

    那么S[k+1...(k+L)-x+1]=S[k+1...p]S[k+1...(k+L)x+1]=S[k+1...p]。

    又因为:

    T[l1...r1-x+1]=T[l2...r2-x+1]=S[k+1...(k+L)-x+1]T[l1...r1x+1]=T[l2...r2x+1]=S[k+1...(k+L)x+1]

    color{blue}{ ext{S1}}color{black}{=}color{red}{ ext{S2}}color{black}{=}color{green}{ ext{S3}}S1=S2=S3。

    所以T[l1...r1-x+1]=S[k+1...p]T[l1...r1x+1]=S[k+1...p],

    也就是说T[1...r1-x+1]=S[k+1...p]T[1...r1x+1]=S[k+1...p],这一段已经匹配成功了。

    color{blue}{ ext{S1}}S1与color{red}{ ext{S2}}S2是相等的,已经匹配成功了。

    那么我们就可以从S[p+1]S[p+1]和T[r1-x+2]T[r1x+2]开始暴力匹配了,无需再考虑前面的东西。

    那么这段的代码长这样:

    int now=extend[p0]+p0-i;
    now=max(now,0);//这里是防止i>p
    while(t[now]==s[i+now]&&now<(int)t.size()&&now+i<(int)s.size())now++;//暴力求解的过程
    extend[i]=now;
    p0=i;//更新p0

    nextnext

    extendextend的大部分过程已经完成了,现在就剩怎么求nextnext了,我们先摸清一下求nextnext的本质:

    求T的每一个后缀与T的最长公共前缀长度

    听起来好熟悉,我们再看一下题面:

    求S的每一个后缀与T的最长公共前缀长度

    我们发现求nextnext的本质和求extendextend的本质是一样的,所以我们直接复制重新打一遍就好了。

    这其实和kmpkmp的思想很相似,因为kmpkmp也是自己匹配一遍自己,再匹配文本串。

    要注意的一点是:求nextnext时我们要从第2位(也就是代码中的第1位)开始暴力,这样能防止求nextnext时引用自己nextnext值的情况。

    时间复杂度

    因为求nextnext的时间复杂度是O(m)O(m),求extendextend的时间复杂度是O(n)O(n),所以总时间复杂度:O(n+m)O(n+m),即SS串与TT串的长度之和。

    题目链接:https://www.luogu.org/problem/P5410

    Code:

    #include<iostream>
    #include<cstdio>
    using namespace std;
    const int maxn=1e6+5;
    string s,t;
    int q,nxt[maxn],extend[maxn];
    void getnxt()
    {
        nxt[0]=t.size();//nxt[0]一定是T的长度
        int now=0;
        while(t[now]==t[now+1]&&now+1<(int)t.size()) now++;
        nxt[1]=now;
        int p0=1;
        for(int i=2;i<(int)t.size();i++)
        {
            if(i+nxt[i-p0]<nxt[p0]+p0) nxt[i]=nxt[i-p0];//第一种情况
            else//第二种情况
            {
                now=nxt[p0]+p0-i;
                now=max(now,0);//这里是防止i>p的情况
                while(t[now]==t[i+now]&&i+now<(int)t.size()) now++;
                nxt[i]=now;
                p0=i;//更新p0
            }
        }
    }
    void exkmp()
    {
        getnxt();
        int now=0;
        while(s[now]==t[now] && now<min((int)s.size(),(int)t.size())) now++;
        extend[0]=now;
        int p0=0;
        for(int i=1;i<(int)s.size();i++)
        {
            if(i+nxt[i-p0]<extend[p0]+p0) extend[i]=nxt[i-p0];//第一种情况
            else//第二种情况
            {
                now=extend[p0]+p0-i;
                now=max(now,0);
                while(t[now]==s[i+now]&&now<(int)t.size()&&now+i<(int)s.size()) now++;
                extend[i]=now;
                p0=i;
            }
        }
    }
    int main()
    {
        cin>>s>>t;
        exkmp();
        int len=t.size();
        for(int i=0;i<len;i++) printf("%d ",nxt[i]);
        printf("
    ");
        len=s.size();
        for(int i=0;i<len;i++) printf("%d ",extend[i]);
        printf("
    ");
        return 0;
    }
    当初的梦想实现了吗,事到如今只好放弃吗~
  • 相关阅读:
    [HDU2866] Special Prime (数论,公式)
    [骗分大法好] 信息学竞赛 骗分导论(论文搬运)
    flayway数据库管理
    RabbitMQ的基本概念与原理
    springboot+ideal实现远程调试
    盘点总结
    mysql查看进程命令
    Java字符串正则文本替换
    springboot代码级全局敏感信息加解密和脱敏方案
    使用PMD进行代码审查
  • 原文地址:https://www.cnblogs.com/caijiaming/p/11310926.html
Copyright © 2011-2022 走看看