【题目来源】
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/