【KMP算法】【最小循环节】讲解 + 例题 POJ 1961 Period 【给字符串s,求s的具有循环节的前缀,并输出所有前缀长,循环节个数】

【KMP算法】【最小循环节】讲解 + 例题 POJ 1961 Period 【给字符串s,求s的具有循环节的前缀,并输出所有前缀长,循环节个数】

摘自 KMP最小循环节

一、定理

假设S的长度为len,若S存在最小循环节,循环节的长度c_L = len-next[len],子串为S[0…len-next[len]-1]c_l表示cycle_len

(1)如果len % (len - next[len]) == 0,则表明字符串S可以完全由循环节循环组成,循环周期T = len / c_L【注意除了满足这个条件外,还需要循环节的个数大于1,不然就一个循环节还叫什么循环】。

(2)如果不能,说明还需要再添加几个字母才能补全。
需要补的个数是num = c_L - len % c_L
又因为c_L = len - next[len]
所以 num = c_L - len % c_L = c_L - (len - c_L) % c_L = c_L - next[len] % c_L

二、讲解及证明

2.1 背景介绍

  • 理解该定理,首先要理解next数组的含义:next[i]表示前面长度为i的子串中,前缀和后缀相等的最大长度。
    如:abcdabc
    |index|0|1|2|3|4|5|6|7|
    |—-|
    |char|a|b|c|d|a|b|C|
    |next|-1|0|0|0|0|1|2|3|

  • 如对于a,ab,abc,abcd,很明显,前缀和后缀相同的长度为0

对于长度为5的子串abcda,前缀的a和后缀的a相同,长度为1

对于长度为6的子串abcdab,前缀的ab和后缀的ab相同,长度为2

2.2 举例说明

接下来举几个例子来说明最小循环节和循环周期:

为方便说明,先设字符串的长度为len,循环子串的长度为L

1.

s0s1s2s3s4s5 ,next[6]=3

即s0s1s2=s3s4s5

很明显可知:循环子串为s0s1s2,L=len-next[6]=3,且能被len整除。

2.

s0s1s2s3s4s5s6s7 ,next[8]=6

此时len-next[8]=2 ,即L=2

由s0s1s2s3s4s5=s2s3s4s5s6s7

可知s0s1=s2s3,s2s3=s4s5,s4s5=s6s7

显然s0s1为循环子串

3.

s0s1s2s3s4s5s6 ,next[7]=4

此时len-next[7]=3,即L=3

由s0s1s2s3=s3s4s5s6

可知s0s1=s3s4,s2s3=s5s6

从而可知s0s1s2=s3s4s5,s0=s3=s6

即如果再添加3-4%3=2个字母(s1s2),那么得到的字符串就可以由s0s1s2循环3次组成

2.3 理论解释

  • 对于一个字符串,如abcd abcd abcd,由长度为4的字符串abcd重复3次得到,那么必然有原字符串的前八位等于后八位。

  • 也就是说,对于某个字符串S,长度为len,由长度为L的字符串s重复R次得到,当R≥2时必然有S[0…len-L-1]=S[L…len-1],字符串下标从0开始

  • 那么对于KMP算法来说,就有next[len]=len-L。此时L肯定已经是最小的了(因为next的值是前缀和后缀相等的最大长度,即len-L是最大的,那么在len已经确定的情况下,L是最小的)。

###2.4 证明
《【KMP算法】【最小循环节】讲解 + 例题 POJ 1961 Period 【给字符串s,求s的具有循环节的前缀,并输出所有前缀长,循环节个数】》

由上,next【i】=j,两段红色的字符串相等(两个字符串完全相等),s[k....j]==s[m....i]

s[x...j] = s[j....i] (xj=ji)

则可得,以下简写字符串表达方式

kj = kx + xj;

mi = mj + ji;

因为xj = ji,所以kx = mj,如下图所示

《【KMP算法】【最小循环节】讲解 + 例题 POJ 1961 Period 【给字符串s,求s的具有循环节的前缀,并输出所有前缀长,循环节个数】》

s[a…x] = s[x..j] (ax = xj)

又由xj = ji,可知ax = xj = ji

s[a…i]是由s[a…x]循环3次得来的。

而且看到没,此时又重复上述的模型,s[k…x] =s [m…j],可以一直递推下去

最后可以就可以递推出文章开头所说的定理了。

2.5 实例

  • abdabdab len:8 next[8]:5

    最小循环节长度:3(即abd) 需要补的个数是1 d

  • ababa len:5 next[5]:3

    最小循环节长度:2(即ab) 需要补的个数是1 b

三、例题 POJ 1961 Period

For each prefix of a given string S with N characters (each character has an ASCII code between 97 and 126, inclusive), we want to know whether the prefix is a periodic string. That is, for each i (2 <= i <= N) we want to know the largest K > 1 (if there is one) such that the prefix of S with length i can be written as A K , that is A concatenated K times, for some string A. Of course, we also want to know the period K.

Input
The input file consists of several test cases. Each test case consists of two lines. The first one contains N (2 <= N <= 1 000 000) – the size of the string S. The second line contains the string S. The input file ends with a line, having the number zero on it.

Output
For each test case, output “Test case #” and the consecutive test case number on a single line; then, for each prefix with length i that has a period K > 1, output the prefix size i and the period K separated by a single space; the prefix sizes must be in increasing order. Print a blank line after each test case.

Sample Input
3
aaa
12
aabaabaabaab
0

Sample Output
Test case #1
2 2
3 3

Test case #2
2 2
6 2
9 3
12 4

题意:
给字符串s,判断s的所有前缀(包括它自己),看谁具有循环节,输出这个前缀的长度,以及循环节个数

AC代码:

#include <iostream>
#include <stdio.h>
#include <string>
#include <string.h>

using namespace std;

const int maxn = 1000005;
int Next[maxn];

void GetNext(string p, int * Next)
{
    int p_len = p.length();
    int i = 0;
    int j = -1;
    Next[0] = -1;
    while(i < p_len)
    {
        if(j == -1 || p[i] == p[j])
        {
            i++;
            j++;
            Next[i] = j;
        }
        else
            j = Next[j];
    }
}

int main()
{
    int n, ks = 1;
    string s;
    while(~scanf("%d", &n) && n)
    {
        cin>>s;
        GetNext(s, Next);
        printf("Test case #%d\n", ks++);
        for(int i = 2; i <= n; i++)
        {
            int cycle_len = i - Next[i];
            int num = i / cycle_len;
            if(i % cycle_len == 0 && num != 1)
                printf("%d %d\n", i, num);
        }
        printf("\n");
    }
    return 0;
}

四、相关题目

① HDU 3746 Cyclic Nacklace
② HUST 1010 The Minimum Length
③ POJ 2406 Power String
④ POJ 2752 Seek the Name, Seek the Fame

    原文作者:KMP算法
    原文地址: https://blog.csdn.net/Floraqiu/article/details/81564212
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞