题意
如果一个字符串 \(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;
}