zoukankan      html  css  js  c++  java
  • KMP && KMP求字符串循环节

    •参考资料

      [1]:KMP学习资料:左神进阶班第一节 

    KMP学习小结

    •KMP的用途

      对于给定的两个串 S,T,如何在线性时间内判断 S 是否包含 T 呢?

      以下默认 S,T 下标从 0 开始;

    •前置知识

      $next$ 数组,定义 $next_i$ 表示 T 中前 i 个字符 ($T_0,T_1,cdots ,T_{i-1}$) 最长前缀和最长后缀的匹配长度并且满足 $next_i < i$;

      并人为规定 $next_0=-1,next_1=0$;

      例如,假设 $T="aaaab"$,$next$ 求解情况如下:

        $egin{aligned} next_0 &= -1 \ next_1 &= 0 \next_2 &=1 (prefix="a",suffix="a") \ next_3&=2 (prefix="aa",suffix="aa")\ next_4&=3 (prefix="aaa",suffix="aaa") \ next_5&=0 (don't exsits prefix = suffix)end{aligned}$;

      $next$ 数组的求解可以通过动态规划在线性时间内完成;

     1 int next[maxn];
     2 void getNext(const char *s)
     3 {
     4     int len=strlen(s);
     5     next[0]=-1;
     6     next[1]=0;
     7     int index=2;
     8     int cnt=0;
     9     while(index <= len)
    10     {
    11         if(s[index-1] == s[cnt])
    12             next[index++]=++cnt;
    13         else if(cnt != 0)
    14             cnt=next[cnt];
    15         else
    16             next[index++]=0;
    17     }
    18 }
    next求解

    •通过KMP判断S是否包含T

      

      假设当前是从 S 的 i 位置匹配 T 的 0 位置,并且 $S[x] eq T[y]$;

      A,B 是通过 $next_y$ 求出的最长前缀和最长后缀的匹配串;

      易得 C=A=B,那么,当前位置失配后,下一个匹配的位置为 S[x] 与 T[z] ;

      并且很容易证明 $[i+1,j-1]$ 中,以任意下标为开始都不会完整的匹配出 T;

      用 $next$ 数组证明如下:

        假设 $[i+1,j-1]$ 中存在一位置 K,从 K 位置出发可以匹配出 T;

        

        那么就有 S[K,......,X-1] = T[0,......,X-1-K](图中的绿色方框);

        又因为 S[K,......,X-1] = T[k,......,Y-1],那么 T[0,......,X-1-K] = T[k,......,Y-1];

        也就是说,Y 之前找到了比 next[Y] 更长的一对相等的最长前缀和最长后缀;

        但是因为 next 数组求解的是正确的,所以,就不会存在 K 位置,使得 S 从 K 位置出发可以匹配出 T。

        证毕。

     1 struct KMP
     2 {
     3     int next[maxn];
     4     void getNext(const char *s)
     5     {
     6         int len=strlen(s);
     7         next[0]=-1;
     8         next[1]=0;
     9         int index=2;
    10         int cnt=0;
    11         while(index <= len)
    12         {
    13             if(s[index-1] == s[cnt])
    14                 next[index++]=++cnt;
    15             else if(cnt != 0)
    16                 cnt=next[cnt];
    17             else
    18                 next[index++]=0;
    19         }
    20     }
    21     bool kmp(const char *s,const char *t)///判断串s是否包含串t
    22     {
    23         getNext(t);
    24         int n=strlen(s);
    25         int m=strlen(t);
    26         int x=0;///s的下标索引
    27         int y=0;///t的下标索引
    28         while(x < n && y < m)
    29         {
    30             if(s[x] == t[y])///s[x]匹配t[y],都++去匹配下一个位置
    31                 x++,y++;
    32             else if(y == 0)///如果y==0,只能让x来到下一个位置
    33                 x++;
    34             else///通过next数组找需要x匹配的位置
    35                 y=next[y];
    36         }
    37         ///如果y == m,说明在串S包含串T
    38         return y == m ? true:false;
    39     }
    40 }_kmp;
    KMP

    应用1-2019ICPC徐州网络赛D.Carneginon

    •题意

      给你一个 串T 和 q 次询问,每次询问给出一个 串S;

      对于每次询问,判断 T 和 S 的包含关系;

      (1)如果 |T| > |S|

        T 包含 S,输出 "my child!";

        反之,输出 "oh, child!";

      (2)如果 |T| < |S|

        S 包含 T,输出 "my teacher!";

        反之,输出 "senior!";

      (3)|T| = |S|

        如果 T = S,输出 "jntm!";

        反之,输出 "friend!";

      并且,题目给出的数据范围 $q imes (|S|+|T|)leq 10^7$,所以每次判断跑一遍 KMP 是完全可以 AC 这道题的;

    •Code

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 const int maxn=1e6+50;
     4 
     5 char s[maxn];
     6 char t[maxn];
     7 struct KMP
     8 {
     9     int next[maxn];
    10     void getNext(const char *s)
    11     {
    12         int len=strlen(s);
    13         next[0]=-1;
    14         next[1]=0;
    15         int index=2;
    16         int cnt=0;
    17         while(index <= len)
    18         {
    19             if(s[index-1] == s[cnt])
    20                 next[index++]=++cnt;
    21             else if(cnt != 0)
    22                 cnt=next[cnt];
    23             else
    24                 next[index++]=0;
    25         }
    26     }
    27     bool kmp(const char *s,const char *t)///判断串s是否包含串t
    28     {
    29         getNext(t);
    30         int n=strlen(s);
    31         int m=strlen(t);
    32         int x=0;///s的下标索引
    33         int y=0;///t的下标索引
    34         while(x < n && y < m)
    35         {
    36             if(s[x] == t[y])///s[x]匹配t[y],都++去匹配下一个位置
    37                 x++,y++;
    38             else if(y == 0)///如果y==0,只能让x来到下一个位置
    39                 x++;
    40             else///通过next数组找需要x匹配的位置
    41                 y=next[y];
    42         }
    43         ///如果y == m,说明在串S包含串T
    44         return y == m ? true:false;
    45     }
    46 }_kmp;
    47 
    48 int main()
    49 {
    50     scanf("%s",t);
    51 
    52     int q;
    53     scanf("%d",&q);
    54     while(q--)
    55     {
    56         scanf("%s",s);
    57 
    58         int n=strlen(t);
    59         int m=strlen(s);
    60 
    61         if(n > m)
    62         {
    63             if(_kmp.kmp(t,s))
    64                 puts("my child!");
    65             else
    66                 puts("oh, child!");
    67         }
    68         else if(n < m)
    69         {
    70             if(_kmp.kmp(s,t))
    71                 puts("my teacher!");
    72             else
    73                 puts("senior!");
    74         }
    75         else if(_kmp.kmp(s,t))
    76             puts("jntm!");
    77         else
    78             puts("friend!");
    79     }
    80 }
    View Code

    对KMP求字符串循环节的理解

    •前置知识

      KMP中的 next 数组;

    •结论

      长度为 len 的字符串, 如果 $(len-next_{len}) | len$ ,则循环次数为 $frac{len}{len-next_{len}}$否则为1。

    •证明

      分三种情况讨论:

        

      情况①:显然  len-next[len] | len , 循环次数为 2

      情况②:len-next[len] 一定不整除 len,证明如下:

        假设 s串 由若干个 a 串和 b 串组成,为方便表述,就固定有4个a串和1个b串,且假设|a| = a , |b| = b:

        

        假设 len-next[len] | len , 即 2a+b | 4a+b;

        由带余除法可得 b = ka+r; ( 0 <= r < a)
        那么 2a+b | 4a+b ⇔ (2+k)a+r | (4+k)a+r;
        相当于 [(4+k)a+r] % [(2+k)a+r] = 0;
        下面我们来化简这个式子:
        [(4+k)a+r] % [(2+k)a+r] = [(2+k)a+r+2a] % [(2+k)a+r] = 2a%[(2+k)a+r];
        易得 (2+k)a+r > 2a;(k和r不会同时为0,除非 b = 0,这就与题设不符了)
        所以 2a % [(2+k)a+r] = 2a ≠0;
        所以假设不成立;
        证毕;

      情况③:(证明待给出)

        (3.1)假设 s串 由长度为 a 的串循环 k 次构成:

        

        len = k×a , next[ len ] = (k-1)×a;

        len - next[len] = a , len / (len - next[len] = k;

        所以可得循环次数为 k;

        (3.2)

        .......................... 

    应用

    •题目描述

      给出一字符串 s,求出最少需要增加多少字符使得字符串 s 变成循环次数 ≥ 2 的串?

    •解析

      1.如果 len%(len-nex[len]) == 0 && nex[len] != 0 ,答案为 0;

      2.如果 $next_{len} < frac{len}{2} $,答案为 $len-2 imes next_{len}$;

      3.如果 $next_{len} geq frac{len}{2} $,设 k = len-next[len],那么答案为 k-len%k;

      情况1显而易见;

      情况2对应的字符串如下图所示:

        

      字符串s的最长公共前缀与最长公共后缀不重合,那么最少需要增加 |b| 个字符,形成 abab 类型的循环串;

      情况3对应的字符串如下图所示:

        

      字符串s的最长公共前缀与最长公共后缀重合,重合长度为 |b|;

      那么只需在末尾补充字符 $s_x,s_{x+1},cdots ,s_{a-1}$ 即可和 串x 一起形成 串a;

      这样的话,串S 就变成了由  串a 循环四次构成的串;

      此时最少需要增加 |a|-|x| 个字符;

      |b| = 2×next[len] - len;

      |a| = next[len] - |b| = len-next[len] = k;

      |x| = len%a;

      |a| - |x| = |a| - len%|a| = k - len%k;

  • 相关阅读:
    Linux(centos7)安装maven3.5
    mysql安装错误之->ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.sock' (2)
    CentOS7 安装tomcat为系统服务器 Systemctl管理Tomcat,并设置开机启动
    linux(centos)设置tomcat开机启动
    linux(centos7)安装tomcat
    修改Tomcat8w.exe可执行路径:Path to executable
    CentOS 7更改yum源与更新系统
    最全的PHP开发Android应用程序
    Cookies的各方面知识(基础/高级)深度了解
    使用Hash直接登录Windows(HASH传递)
  • 原文地址:https://www.cnblogs.com/violet-acmer/p/10480077.html
Copyright © 2011-2022 走看看