首先加深以下对next数组的理解(kmp算法见基础算法专栏),next它的核心是这个公式:
next[j] = k;
它的含义是在模式字符串p中,[ 0 , j )这一子串的最长匹配数(前缀与后缀相同的最长个数)为k,所以若在p[j] 处失配,会回溯到next[k]进行匹配,举个例子:
a | b | c | g | a | b | c | x |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
k | j |
字符串p在p[7]处失配,而p[7] = 3,说明[0,7)的最长匹配数为3,且为p[7]之前的3位和p[k]之前的三位。进一步拓展,可以研究next数组的前缀周期性,仍举个例子:
a | b | c | a | b | c | a | b | c | x |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
k | j |
根据之前的描述,不难分析出p[9] = 6,与之前不同的是这个例子中的前缀具有周期性,周期单位为abc,周期次数为3,遵循下列公式
判断是否为周期:j%(j-k)== 0 (成立则为周期) 周期次数 = j/(j-k)
可以这样理解:若前缀有周期性,那么j-k必然为所有周期中的一个周期。
下面看一道例题:
Period
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 2398 Accepted Submission(s): 1187
Problem Description
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 AK , 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
思路:
构建next数组结合周期性判断即可,但要注意与kmp算法略微不同的是,构造的next的规模比字符串p的规模大1([ 0 , n]),因为最后一个字符也算入周期性判断,记录在next[n]处,而kmp算法不把字符串p整串当做其中一个子串,代码如下:
#include <bits/stdc++.h>
using namespace std;
char p[1000010];
int next[1000010];
int n;
void ccnext(int p_len);
void print(int p_len);
int main() {
for(int i=1;;i++) {
scanf("%d",&n);
if(n==0) break;
getchar();
gets(p);
printf("Tedt case #%d\n",i);
ccnext(n);
print(n);
memset(next,0,sizeof(next));
}
return 0;
}
void ccnext(int n) {//构建next数组
next[0] = -1;
next[1] = 0;
int i = 1;
int k = 0;
while(i<n) {//注意此处的边界
if(k<0 || p[k]==p[i]) {
next[++i] = ++k;
} else {
k = next[k];
}
}
}
void print(int n) {
for(int i=0;i<=n;i++) {
if(next[i]==0 || next[i]==-1)
continue;
else {
int t = i-next[i];
if(i%t==0) //整除即存在周期性
printf("%d %d\n",i,i/t);
}
}
}
(例题出自HUDOJ 1358)
以上~