KMP中next[]数组与循环节:周期

题目链接:https://www.acwing.com/problem/content/description/143/

参考文献:https://www.acwing.com/solution/content/4614/

题目:

一个字符串的前缀是从第一个字符开始的连续若干个字符,例如 abaab 共有 5 个前缀,分别是 aababaabaaabaab

我们希望知道一个 N 位字符串 S 的前缀是否具有循环节。

换言之,对于每一个从头开始的长度为 i(i>1)的前缀,是否由重复出现的子串 A 组成,即 AAA…A(A重复出现 K 次,K>1)。

如果存在,请找出最短的循环节对应的 K 值(也就是这个前缀串的所有可能重复节中,最大的 K值)。

输入格式

输入包括多组测试数据,每组测试数据包括两行。

第一行输入字符串 S 的长度 N。

第二行输入字符串 S。

输入数据以只包括一个 0的行作为结尾。

输出格式

对于每组测试数据,第一行输出 Test case # 和测试数据的编号。

接下来的每一行,输出具有循环节的前缀的长度 i 和其对应 K,中间用一个空格隔开。

前缀长度需要升序排列。

在每组测试数据的最后输出一个空行。

数据范围

2≤N≤1000000

输入样例:

3
aaa
4
abcd
12
aabaabaabaab
0

输出样例:

Test case #1
2 2
3 3

Test case #2

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

 关于KMP中next[]数组与循环节的知识:

1.如果i % ( i - next[i] ) == 0, 则字符串 1~ i - next[i]为其最小的循环节。并且此字符串的循环节个数最多为 i / (i - next[i]);

2.如果 i - next[?]能够得到 i % (i - next[?]) == 0, 则 i - next[?]也为其循环节,但不是最小的循环节。此循环节的个数为 i / (i - next[?]);

3.如果 i % (i - next[i]) != 0, 则此字符串不存在能够组成完整字符串的循环节。

4.next[i]存在,循环节未必存在: 如 abcabcab中,next[8] = 5,  但是对于i而言不存在循环节。但是 i - next[i]必为重复的,只是最后的一段无法完全满足条件。8 -5 == 3, 所以为abc,abc不断循环。

证明1和4:前 1 ~ i - next[i]为其循环节

 情况1:i为 i - next[i]的整数倍,设i - next[i] 为m

即i = k * m

 根据①为1 ~ m进行切割,则①==②,② == ③,以此类推(多个①②③序列,直到长度只剩下 <= 2 * m, 的最后两段。),最后一定可以分割到④+⑤的情况,子序列④的含义为 <= m 的子序列 + ⑤,  由m为 i - next[i]长度可知,序列⑤的长度为m.

而i = k *m, 所以字符串④的长度也必定为m,则③ == ④,④ == ⑤。

所以①子序列(也就是1~i - next[i])为其循环节。

情况2:如果i != k *m的话,则最后的两段④ + ⑤中,④的长度一定小于m

 由此可知,从⑤中切出一个⑥,使得④ + ⑥的长度为m,则 ③ == ④ + ⑥,④ == ⑤ - ⑥,而④又 == ③ - ⑥,所以可以知道,③的前部分与④的子序列是相同的,

所以可以知道,1~i的字符串由多个子序列① + 子序列①的前 i - k*m(值小于i的值)部分组成。

那么为什么i - next[i]为其最小的循环节呢?

因为next数组的含义是能够再次重新与所要配对的字符串能够匹配的最大长度,所以next[i]的值代表满足条件的最大值,而i - next[i]自然就是最小长度序列①了。所以1~i - next[i]的序列①为其最小的循环节。

从而可以推出结论2,但是循环节的长度一定为, i - next[next[i]], i - next[ next[ next[i] ] ]等等才能可能完整的构成字符串。

而由上面的证明中的情况2,就可以证明不存在能够组成完整字符串的循环节。需要组成的字符串要比循环节少了最后一部分少了几个字符。

代码:

# include <iostream>
using namespace std;
const int N = 1000010;

char ch[N];
int ne[N];

int n;

int main()
{
    int t = 1;
    while(scanf("%d",&n) , n)
    {
        printf("Test case #%d\n",t++);
        cin >> ch + 1;
        for(int i = 2 , j = 0 ; i <= n ; i++)  //求next数组
        {
            while(j && ch[j + 1] != ch[i])
            {
                j = ne[j];
            }
            if(ch[j + 1] == ch[i])
            {
                j++;
            }
            ne[i] = j;
        }
        
        for(int i = 1 ; i <= n ; i++)
        {
            int x = i - ne[i]; // 求循环节长度
            //由于重复出现次数要大于1,所以不能为整个长度
            if(x != i && i % x == 0)
            {
                printf("%d %d\n",i,i / x);
            }
        }
        printf("\n");
    }
    return 0;
}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值