Period

发布时间 2023-08-09 11:35:48作者: HEIMOFA

Period UVA

题面翻译

对于给定字符串 \(S\) 的每个前缀,我们想知道它是否为周期串(周期串定义为由若干最小循环节拼接而成的字符串),若是,输出前缀长度和循环节数量。


输入格式

多组数据,每组数据第一行一个整数 \(n\),表示字符串 \(s\) 的长度,若 \(n=0\) 则结束输入。

\(n\ne0\),则第二行一个字符串 \(S\),如题意。


输出格式

对于第 \(i\) 组输出,第一行一个字符串 Test case #i

之后若干行一行两个整数,表示如果字符串 \(S\) 的一个前缀是周期串,它的长度和循环节数量(注意这里的循环节是指长度最小的循环节)。

之后再空一行。


样例输入

3
aaa
12
aabaabaabaab
0

样例输出

Test case #1
2 2
3 3

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

提示

\(n\leq10^6\),字符串 \(S\) 均由小写字母构成。


这道题的难点在于如何判断 \(S[1\sim j]\)\(S[1\sim i](j<i)\)最小循环节

首先有一个很明显的结论——\(j\) 能够整除 \(i\)

其次有一个不是很明显的结论——当 \(j\) 能够整除 \(i\)\(S[1\sim i-j]=S[j+1\sim i]\) 时,\(S[1\sim j]\)\(S[1\sim i](j<i)\) 的最小循环节。

证明

因为 \(j\) 能够整除 \(i\),所以 \(j\) 也能够整除 \((i-j)\)

也就是说 \(S[1\sim i-j]\)\(S[j+1\sim i]\) 的长度应当都为 \(j\) 的倍数。

那么分别取两个字符串的前 \(j\) 项,显然也相等。

再取后面 \(j\) 项,显然还是相等。

由于是 \(j\) 的倍数,所以肯定刚好取完。

也就是说这两个字符串以 \(j\) 为间隔错位相等的。

那么很明显,最后整个字符串也自然以 \(j\) 为循环节展开。

题目要求最小循环节,那么就要让 \(S[1\sim i-j]\)\(S[j+1\sim i]\) 的长度最长。

这个东西是不是十分眼熟,它就是 KMP 算法中的核心——\(next\) 数组。

接下来思路是不是就非常清晰了,求出 \(next\) 数组后枚举 \(i\),判断是否整除且循环节个数大于 \(1\),如果两个都满足的话输出就行了。

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,t;
const int N=1e6+5;
int nxt[N];
char x[N];

void get(char *s){
	nxt[1]=0;
	int len=strlen(s+1);
	for(int i=2,j=0;i<=len;i++){
		while(j>0&&s[i]!=s[j+1]) j=nxt[j];
		if(s[i]==s[j+1]) j++;
		nxt[i]=j;
	}
}
signed main()
{
	while(scanf("%lld",&n),n){
		scanf("%s",x+1);
		get(x);
		printf("Test case #%lld\n",++t);
		for(int i=2;i<=n;i++){
            //i为前缀的长度
            //(i-nxt[i])为最小循环节的长度
            //i/(i-nxt[i])为最小循环节的个数
			if(i%(i-nxt[i])==0&&i/(i-nxt[i])>1){
				printf("%lld %lld\n",i,i/(i-nxt[i]));
			}
		}
		puts("");
	}
	return 0;
}