zoukankan      html  css  js  c++  java
  • EXKMP模版

    这道题目折腾了我好一会啊,出于尊重我要先放我们师兄的博客

    1178: [视频]EXKMP模版:最长共同前缀长度

    时间限制: 1 Sec  内存限制: 128 MB
    提交: 180  解决: 123
    [提交] [状态] [讨论版] [命题人:admin]

    题目描述

    【题目描述】
    给出模板串A和子串B,长度分别为lenA和lenB,要求在线性时间内,对于每个A[i](1<=i<=lenA),求出A[i..lenA]与B的最长公共前缀长度
    【输入格式】
    输入A,B两个串,(lenB<=lenA<=1000000)

    【输出格式】
    输出lenA个数,表示A[i...lenA]与B的最长公共前缀长度,每个数之前有空格

    【样例输入】
    aabbabaaab
    aabb
    【样例输出】

    4 1 0 0 1 0 2 3 1 0

    我们就引入了一个叫EXKMP的东西,接下来看下面的解释

    一个非常重要的提醒,你们看在博客的时候遇到图一定要拖出来放大看,因为颜色在模糊处会重合

    首先看到EXKMP,是不是很容易联想到我们上一次提到的KMP呢?回忆一下KMP

    KMP:p[ ]表示以结尾为首和以开头为首的最长公共字串 (p[ ]是作为KMP的一个重要数组)

    EXKMP:extend[ ]表示以i为首以开头为首的最长公共前缀长度 (extend[ ]是作为EXKMP的一个重要数组)
    目的:st[1~extend[i]]=st[i~i+extend[i]-1] (st表示原来的字符串)
    extend[1]=len,因为以第一个字母为首的最长公共前缀就是整个字符串

    我先在这里讲清楚,extend数组的意义一定要记住,因为这个东西在后面的证明当中是非常非常重要的

    很好我们现在已经把EXKMP当中的最基础的比较重要的给讲清楚了,要注意一下,EXKMP和KMP一样也是对单串进行处理

    显然作为一个非常害人的算法,怎么可能就这么一点东西呢?

    k表示当前搜索过的范围以为能到达的最远编号
    p表示k+extend[k]-1,(也就是说k+extend[k]-1最大)
    p记录以k为首以开头为首的最长公共前缀长度,k+最长公共前缀长度后所到达的编号

    因为st[1~extend[k]]=st[k~p]   (中间的波浪线表示某到某)

    化简来说就是  st[1~extend[k]]= st[k~k+extend[k]]-1] 这个我相信是没有任何问题的
    所以extend[k]-1+1=p-k+1
      extend[k]=p-k+1
    所以 st[1~p-k+1]=st[k~p]

    这个是真的很好看出来,不过我还是在这里说清楚,这个p的含义很重要,p-k+1在后面也有比较大的用处

    定义L=extend[i-k+1]
    st[i-k+1~i-k+1+L-1]=st[i-k+1~i-k+L] (向右延伸L的长度) 
    st[i-k+1~i-k+L]=st[1~L] 这个的话可能需要感性理解一下这个延伸的含义,就是说我们如果是相同的区间,延伸相同的长度,那么延伸之后也是相同的
    然后把p,k,L,i合在一起
    因为L的不定性,我们就要考虑 i-k+L 和 p-k+1 的大小 (分情况讨论)

    1.i-k+L<p-k+1
    从图中我们可以看到
    因为st[1~L]=st[i-k+1~i-k+L]
      st[i-k+i~p-k+1]=st[i~p]
    所以在st[i~p]当中一定含有一段从i开始和 st[1~L]是完全相同的,所以 st[i~i+L-1] 是与之完全相同的 (蓝紫色位置)


    因为 st[i-k+1~p-k+1]=st[i~p]
    又因为 i-k+L<p-k+1
    所以我们得到 st[i-k+L+1]=st[i+L] (蓝色位置)


    又因为extend[i-k+ 1]的定义
    所以 st[L+1] (紫色位置)!=st[i-k+L+1]
    因为 st[i-k+L]=st[i+L-1] -> st[i-k+L+1]=st[i+L]
    又因为 st[L+1]!=st[i-k+L+1]
    所以可以得到 : st[i+L]!=st[L+1] ,(其实就是证明了紫色和第二个黄色是不匹配的)
    那么extend[i]就直接等于L了(最大字串前缀长度,不匹配就为原来匹配的最大值)

    所以第一种情况就是直接记录L

    2.i-k+L>p-k+1
    从图中我们可以看到

    因为 st[1~L]=st[i-k+1~i-k+L] (蓝紫色)
    又因为 i-k+L>p-k+1

    所以在 st[1~L]中肯定含有一段和 st[i-k+1~p-k+1] 完全相同(绿色)


    因为extend[k]的意义
    所以st[p+1]!=st[p-k+2] (第二个紫色和黄色位置)
    不然的话橙色的长度应该往后延伸,但是并没有,因为extend的定义确定了他的最长公共字串前缀


    又因为st[1~L]=st[i-k+1~i-k+L]
    所以st[p-i+2] (第一个紫色位置)=st[p-k+2]
    因为我们绿色部分相同,而且蓝紫色部分也相同,那就证明我们各自往后一格也是相同的。
    那么st[p-i+2]!=st[p+1],所以extend[i]就等于p-i+1

    3.i-k+L=p-k+1
    从图中我们可以看到
    因为st[i-k+1~i-k+L]=st[1~L]
      st[i-k+1~p-k+1]=st[i~p]
    又因为 i-k+L=p-k+1
    所以 st[1~L]=st[i-k+1~p-k+1] (可换成i-k+L) =st[i~p] (绿色)


    那么由于 extend[i-k+1] 和 extend[k] 的意义表达
    可以得到 st[L+1]!=st[i-k+L+1](第一个紫色不等于蓝色)
        st[i-k+L+1]!=st[p+1]


    但是我们不能确定 st[L+1] 和 st[p+1] 是否相同,我们只能确定从i开始和
    从1开始有 p-i+1这么长的公共前缀,但并不一定是最长的

    那么我们就设一个变量j=p-i+1,表示当前从i开始和从1开始的公共前缀长度

    由于上面这句话,可以直接从st[j+1] 和 st[i+j] 来累加j的值来得出最长的公共前缀(直接一个一个往后匹配)


    xixixixi.细节上面的问题
    因为p是一个不定的数(由k和extend[k]来决定)
    所以说p-i+1是有可能为负数的
    那么第二种情况显然不对
    公共前缀肯定不可以为负数,最小也只能是0
    所以我们就要想要在第二种情况下去 p-i+1和0 的最大值
    但是这是不对的
    因为我们如果直接粗鲁的求最大值,那么我么直接就是0了,但是我们不能判断i+1这个格子(蓝色) 和 1+1这个格子(紫色)是否相同的情况下直接输出0,自然是有问题的

    从图中我们可以看到,因为p-i+1是负数
    所以上面所有的条件都用不了
    因为i对后面的字符没有作任何的计算
    但这个时候是绝对不可以肯定st[1] 和 st[i]是不同的(也就是最长公共前缀为0)
    所以我们需要从st[1]和st[i]开始判断后面的字符是否相同,
    然后我们知道在第二种情况的时候也出现了一个个往后面匹配,
    所以我们把第二种情况和第三种情况归纳为一种情况

    但是....
    这种做法仅仅是匹配单个字符串的,但是这个时候我们要把A串和B串一起匹配
    这个时候我们可以就是一开始定一个数组ex[i],表示从i-lena与B的最长公共前缀长度
    一开始1不能直接等于len
    所以我们直接从1开始到while到后面发现不同的时候就退出,
    然后取前面和他一样长的,也就是说:我们的ex是通过一个个匹配得出来的

    一直到这里,我们对单串的EXKMP的处理也就结束了,这个东西的话,还是自己手动画图模拟一遍,不难懂但是一定要很细心很细心

    接下来看代码的实现

    (注释版:我建议就看注释版先,因为我们知道细节的存在,让我们的代码变得如此“可爱”)

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<cstdlib>
     4 #include<algorithm>
     5 #include<cmath>
     6 #include<iostream>
     7 char sa[1000010],sb[1000010];
     8 int ex[1000010],p[1000010],lena,lenb;
     9 /*p[]表示b串单独匹配自己的字符串,也就是extend数组
    10 ex[]表示把a串和b串互相匹配*/
    11 inline void EXKMP()
    12 {
    13     p[1]=lenb;/*整个字符串长度*/
    14     int i=1,k; 
    15     while(sb[i]==sb[i+1] && i+1<=lenb) i++;/*通过这种方式求出p[2]*/
    16     p[2]=i-1; k=2;/*k表示当前搜索过的范围以为能到达的最远编号*/
    17     int tp,l;/*tp表示k+extend[k]-1,tp记录以k为首和以开头为首的最长公共前缀长度,k+最长公共前缀长度后所到达的编号*/
    18     for(int i=3;i<=lenb;i++)
    19     {
    20         tp=k+p[k]-1; l=p[i-k+1];/*p表示k+extend[k]-1,L=extend[i-k+1]*/
    21         if(i-k+l<tp-k+1) p[i]=l;/*第一种情况,直接记录就可以了*/
    22         else/*第二种和第三种归为一种*/
    23         {
    24             int j=tp-i+1;/*定义一个变量j=p-i+1,表示当前从i开始和从1开始的公共前缀长度*/
    25             if(j<0) j=0;/*因为有可能有负数,所以直接变成0*/
    26             /*但是这两种合并为一种情况,为什么可以这么写呢?
    27             因为p-i+1为正数的时候,j还是保留原来匹配的位置,是负数的话从0开始也是可以匹配1~i和j+1~i+j
    28             只不过这个时候的j等于0而已,所以可以直接这样子做,比较方便*/
    29             while(sb[j+1]==sb[i+j] && i+j<=lenb) j++;/*判断一下i+j<=lenb*/
    30             p[i]=j;/*得出长度*/
    31             k=i;/*更新一下k*/
    32         }
    33     }
    34     ex[1]=lenb;/*ex匹配a串,一开始等于lenb,是不清楚的,只是暂且变成这个值*/
    35     for(int i=1;i<=lenb;i++) if(sa[i]!=sb[i]){ex[1]=i-1;break;}
    36     /*位置不一样,就记录上一个答案,然后退出*/
    37     /*如果没有定义的话ex[1]=lenb的话,会在这里一直一直相同就跳不出来,而且还是等于lenb*/
    38     k=1;
    39     for(int i=2;i<=lena;i++)/*因为1是通过匹配得到的,所以可以直接从2开始*/
    40     {
    41         tp=k+ex[k]-1; l=p[i-k+1];
    42         /*tp也就是说我现在所到达的k最长的时候加上我这个和b串匹配的时候得到的长度
    43           l表示在b串原串匹配到的i-k+1,这样就可以很方便的从b串的开头
    44           定义:extend[i]表示以i为首和以开头为首的最长公共前缀长度,这个条件是保持在同一字符串里面的,
    45           在这里就变成了:以a串的i为首和以b串的开头为首的最长公共前缀长度,
    46           所以这样就可以求出a串匹配b串的ex数组*/
    47         if(i-k+l<tp-k+1) ex[i]=l; 
    48         else
    49         {
    50             int j=tp-i+1;
    51             if(j<0); j=0;
    52             while(sb[j+1]==sa[i+j] && i+j<=lena && j<=lenb) j++;
    53             ex[i]=j; k=i;
    54         }
    55     }
    56 }
    57 int main()
    58 {
    59     scanf("%s%s",sa+1,sb+1);
    60     lena=strlen(sa+1); lenb=strlen(sb+1);
    61     EXKMP();
    62     for(int i=1;i<lena;i++) printf("%d ",ex[i]);
    63     printf("%d\n",ex[lena]);
    64     return 0;
    65 }
    Tristan Code 注释版

     (非注释版:这个的话理解了之后用这个作为自己回忆的方式,还是可以的)

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<cstdlib>
     4 #include<algorithm>
     5 #include<cmath>
     6 #include<iostream>
     7 char sa[1000010],sb[1000010];
     8 int ex[1000010],p[1000010],lena,lenb;
     9 inline void EXKMP()
    10 {
    11     p[1]=lenb;
    12     int i=1,k; 
    13     while(sb[i]==sb[i+1] && i+1<=lenb) i++;
    14     p[2]=i-1; k=2;
    15     int tp,l;
    16     for(int i=3;i<=lenb;i++)
    17     {
    18         tp=k+p[k]-1; l=p[i-k+1];
    19         if(i-k+l<tp-k+1) p[i]=l;
    20         else
    21         {
    22             int j=tp-i+1;
    23             if(j<0) j=0;
    24             while(sb[j+1]==sb[i+j] && i+j<=lenb) j++;
    25             p[i]=j;
    26             k=i;
    27         }
    28     }
    29     ex[1]=lenb;
    30     for(int i=1;i<=lenb;i++) if(sa[i]!=sb[i]){ex[1]=i-1;break;}
    31     k=1;
    32     for(int i=2;i<=lena;i++)
    33     {
    34         tp=k+ex[k]-1; l=p[i-k+1];
    35         if(i-k+l<tp-k+1) ex[i]=l; 
    36         else
    37         {
    38             int j=tp-i+1;
    39             if(j<0); j=0;
    40             while(sb[j+1]==sa[i+j] && i+j<=lena && j<=lenb) j++;
    41             ex[i]=j; k=i;
    42         }
    43     }
    44 }
    45 int main()
    46 {
    47     scanf("%s%s",sa+1,sb+1);
    48     lena=strlen(sa+1); lenb=strlen(sb+1);
    49     EXKMP();
    50     for(int i=1;i<lena;i++) printf("%d ",ex[i]);
    51     printf("%d\n",ex[lena]);
    52     return 0;
    53 }
    TristanCode 非注释版
    我们最终都要成长,最终都要和稚嫩的自己告别.
  • 相关阅读:
    Java 学习笔记- classpath classpath*
    Java this关键字 学习笔记
    Java 基础 类加载器和双亲委派机制 学习笔记
    《Java语言实现快速幂取模》
    《2017年内蒙古自治区第十二届大学生程序设计-超级密码》
    《快速排序》
    《01-背包问题-点菜》
    微信小程序相关二、css介绍,菜单制作,表单相关,京东注册页面
    微信小程序相关一、模仿京东静态登录页面
    分别用js和css实现瀑布流
  • 原文地址:https://www.cnblogs.com/Tristanjiang/p/11358753.html
Copyright © 2011-2022 走看看