AcWing 141:周期 ← KMP 算法 next 数组判断“循环节”

【题目来源】
https://www.acwing.com/problem/content/143/

【题目描述】
一个字符串的前缀是从第一个字符开始的连续若干个字符,例如 abaab 共有 5 个前缀,分别是 a,ab,aba,abaa,abaab。
我们希望知道一个 N 位字符串 S 的前缀是否具有循环节。
换言之,对于每一个从头开始的长度为 i(i>1)的前缀,是否由重复出现的子串 A 组成,即 AAA...A (A 重复出现 K 次,K>1)。
如果存在,请找出最短的循环节对应的 K 值(最短的循环节出现的次数 K 。也就是这个前缀串的所有可能重复节中,最大的 K 值)。

【输入格式】
输入包括多组测试数据,每组测试数据包括两行。
第一行输入字符串 S 的长度 N。
第二行输入字符串 S。
输入数据以只包括一个 0 的行作为结尾。

【输出格式】
对于每组测试数据,第一行输出 Test case # 和测试数据的编号。
接下来的每一行,输出具有循环节的前缀的长度 i 和其对应 K,中间用一个空格隔开。
前缀长度需要升序排列。
在每组测试数据的最后输出一个空行。

【数据范围】
2≤N≤10^6

【输入样例】
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)非平凡前缀,指除了最后一个字符以外,一个字符串的全部头部组合。简称前缀。
(2)非平凡后缀,指除了第一个字符以外,一个字符串的全部尾部组合。简称后缀。
(3)最长公共前后缀,指一个字符串的最长且相等的前缀与后缀。例如,字符串 ABCxyzABC 的最长公共前后缀为 ABC。

● next 数组的涵义:next[i] 表示字符串前 i 个字符的最长公共前后缀长度。
例如,字符串 "ABABA" 的前 3 个字符 "ABA" 的最长公共前后缀为 "A",长度为 1;前 5 个字符 "ABABA" 的最长公共前后缀为 "ABC",长度为 3。
这可由字符串 "ABABA" 的 next 数组(字符串下标从 1 开始)进行验证。例如,next[3]=1,next[5]=3。

|---------|---|---|---|---|---|
| i | 1 | 2 | 3 | 4 | 5 |
| T | A | B | A | B | A |
| ne[i] | 0 | 1 | 1 | 2 | 3 |

● 假设字符串 ‌s[1 ... i]‌ 由 ‌k‌ 个循环节 ‌P 构成,总长度 ‌i=kL‌。其中,L 为循环节长度。
由 next 数组的涵义知,next[i]‌ 等于 ‌k-1‌ 个循环节的总长度,即 ‌next[i]=(k-1)L‌。
由 ‌i=kL‌ 和 ‌next[i]=(k-1)L‌ 联立得:‌L=i-next[i]‌‌

● 当 i%(i-next[i])==0 时,存在循环节。
循环节长度 L=i-next[i],循环次数 K=i/L=i/(i-next[i])。
例如,aabaabaabaab 的 next 数组如下表所示(字符串下标从 1 开始)。

|---------|---|---|---|---|---|---|---|---|---|----|----|----|
| i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| T | a | a | b | a | a | b | a | a | b | a | a | b |
| ne[i] | 0 | 1 | 2 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |

若 i=12 且 next[i]=9,则循环节长度 L=i-next[i]=12-9=3,循环次数 K=i/L=i/(i-next[i])=12/3=4,表示该前缀(或称子串)由长度为 3 的循环节重复 4 次构成‌。

【算法代码】

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

const int maxn=1e6+5;
int ne[maxn];
int cnt;

void getNext(string t) {
    int len=t.length();
    int i=0, j=-1;
    ne[0]=-1;
    while(i<len) {
        if(j==-1 || t[i]==t[j]) {
            ne[++i]=++j;
        } else j=ne[j];
    }
}

int main() {
    string s;
    int n;
    while(cin>>n) {
        if(n==0) break;
        cin>>s;
        cnt++;
        cout<<"Test case #"<<cnt<<endl;
        getNext(s);
        for(int i=2; i<=n; i++) {
            if(i%(i-ne[i])==0 && i/(i-ne[i])>1) {
                cout<<i<<" "<<i/(i-ne[i])<<endl;
            }
        }
        cout<<endl;
    }

    return 0;
}

/*
in:
3
aaa
4
abcd
12
aabaabaabaab
0

out:
Test case #1
2 2
3 3

Test case #2

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

【参考文献】
https://blog.csdn.net/hnjzsyjyj/article/details/146067250
https://www.acwing.com/solution/content/270870/

相关推荐
萌の鱼11 天前
leetcode 686. 重复叠加字符串匹配
数据结构·c++·算法·leetcode·kmp
芥末虾4 个月前
【优选算法】KMP模式匹配算法 {算法介绍;算法原理:核心原理,如何求next数组;代码实现}
c语言·c++·算法·kmp·字符串模式匹配
寻找码源4 个月前
【头歌实训:利用kmp算法求子串在主串中不重叠出现的次数】
c语言·数据结构·算法·字符串·kmp
_OLi_4 个月前
力扣 LeetCode 459. 重复的子字符串(Day4:字符串)
算法·leetcode·职场和发展·kmp
AnFany4 个月前
LeetCode【0028】找出字符串中第一个匹配项的下标
python·算法·leetcode·字符串·kmp·字符串匹配
王能5 个月前
Kotlin真·全平台——Kotlin Compose Multiplatform Mobile(kotlin跨平台方案、KMP、KMM)
android·ios·kotlin·web·android jetpack·kmp·kmm
数学收藏家6 个月前
剪花布条(KPM模板题)
kmp
Qres8216 个月前
8.26 T4 日记和编辑器(fhq维护kmp——kmp本身含有的单射与可合并性)
kmp·平衡树·fhq
理论最高的吻7 个月前
28. 找出字符串中第一个匹配项的下标【 力扣(LeetCode) 】
c++·数学·算法·leetcode·职场和发展·字符串·kmp