算法竞赛进阶指南-55.周期
题目链接
一个字符串的前缀是从第一个字符开始的连续若干个字符,例如 abaab
共有 个前缀,分别是 a
,ab
,aba
,abaa
,abaab
。
我们希望知道一个 位字符串 的前缀是否具有循环节。
换言之,对于每一个从头开始的长度为 ()的前缀,是否由重复出现的子串 组成,即 ( 重复出现 次,)。
如果存在,请找出最短的循环节对应的 值(也就是这个前缀串的所有可能重复节中,最大的 值)。
输入格式
输入包括多组测试数据,每组测试数据包括两行。
第一行输入字符串 的长度 。
第二行输入字符串 。
输入数据以只包括一个 的行作为结尾。
输出格式
对于每组测试数据,第一行输出 Test case #
和测试数据的编号。
接下来的每一行,输出具有循环节的前缀的长度 和其对应 ,中间用一个空格隔开。
前缀长度需要升序排列。
在每组测试数据的最后输出一个空行。
数据范围
输入样例:
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
Method : KMP
的含义:对于字符串的前缀子串,是该前缀子串所有相等的前后缀里长度的最大值。
令,则是最小循环节,并且如果,则题目无解。
证明1:是一个最小循环节。
反证法:假设,
那么有,与是所有相等的前后缀里长度的最大值这一点矛盾。
证明2:如果字符串中存在更大的循环节,则必然是的整数倍,即。
反证法:假设,且,
那么设,易知,因此。
由裴蜀定理,可知成立,不妨假设,那么对于原字符串:
得到,由定义可知是一个循环节,又因为,与是最小循环节矛盾。
#include <string>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e6 + 7;
int n;
string s;
vector<int> get_next(string& p) {
int m = p.size();
vector<int> nxt(m);
nxt[0] = -1;
for (int i = 1, j = -1; i < m; i ++) {
while (~j && p[i] != p[j + 1]) j = nxt[j];
if (p[i] == p[j + 1]) j ++;
nxt[i] = j;
}
return nxt;
}
int main() {
int T = 1;
while (scanf("%d", &n), n) {
cin >> s;
vector<int> nxt = get_next(s);
printf("Test case #%d\n", T ++);
for (int i = 1; i < n; i ++) {
int t = i - nxt[i];
if (t != 0 && (i + 1) % t == 0 && (i + 1) / t > 1) {
printf("%d %d\n", (i + 1) , (i + 1) / t);
}
}
puts("");
}
return 0;
}
复杂度分析
时间复杂度:, 其中为函数的时间复杂度。
空间复杂度:, 数组实际上只用了的空间。
本博客所有文章均采用 CC BY-NC-SA 4.0 协议 ,禁止商用,转载请注明出处!