原题链接

题意

如果一个字符串 \(S\) 是由一个字符串 \(T\) 重复 \(K\) 次形成的,则称 \(T\)\(S\)循环元。使得 \(K\) 最大的字符串 \(T\) 称为 \(S\) 最小循环元

现在给定一个长度为 \(N\) 的字符串 \(S\),对 \(S\) 的每一个前缀 \(S[1 \sim i]\)。如果它的最大循环次数大于 \(1\),则输出该前缀的最小循环元长度和最大循环次数。

引理

\(S[1 \sim i]\) 具有长度为 \(len < i\) 的循环元的充要条件是 \(len\) 能整除 \(i\) 并且 \(s[len+1 \sim i]=s[1 \sim i-len]\)。(即 \(i-len\)\(next[i]\) 的“候选项”)。

证明

必要性:设 \(S[1 \sim i]\) 具有长度为 \(len\) 的循环元,显然 \(len\) 能整除 \(i\)。并且 \(S[1 \sim i-len]\)\(S[len+1 \sim i]\) 都是由 \(i/len -1\) 个循环元构成的。故 \(S[1 \sim i-len]=S[len+1 \sim i]\)

充分性:设 \(len\) 能整除 \(i\) ,并且 \(S[1 \sim i-len]=S[len+1 \sim i]\)。因为 \(len < i\),所以 \(S[1 \sim i-len]\)\(S[len+1 \sim i]\) 的长度不小于 \(len\) 且是 \(len\) 的倍数。二者取前 \(len\) 个字符,有 \(S[1 \sim len]=S[len+1 \sim len*2]\)。以此类推,故 \(S[1 \sim len]\)\(S\) 的循环元。


根据引理,当 \(i-next[i]\) 能整除 \(i\) 时,\(S[1 \sim i-next[i]]\) 就是 \(s[1 \sim i]\) 的最小循环元。进一步的,如果 \(i-next[next[i]]\) 能整除 \(i\),那么 \(S[1 \sim i-next[next[i]]]\) 的次小循环元。

(以上思路来源于《算法竞赛进阶指南》)

code:

#include<cstdio>
#include<cstring>
using namespace std;
const int N=1e6+10;
int n,ne[N];
char s[N];
int main()
{
	int T=0;
	while(scanf("%d",&n),n)
	{
		memset(ne,0,sizeof(ne));
		scanf("%s",s+1);
		printf("Test case #%d\n",++T);
		for(int i=2,j=0;i<=n;i++)
		{
			while(j&&s[i]!=s[j+1]) j=ne[j];
			if(s[i]==s[j+1]) j++;
			ne[i]=j;
			if(i%(i-ne[i])==0&&ne[i]) printf("%d %d\n",i,i/(i-ne[i]));
		}
		puts("");
	}
	return 0;
}