【POJ1961 Period】【KMP】

题面

一个字符串的前缀是从第一个字符开始的连续若干个字符,例如"abaab"共有5个前缀,分别是a, ab, aba, abaa, abaab。

我们希望知道一个N位字符串S的前缀是否具有循环节。换言之,对于每一个从头开始的长度为 i (i 大于1)的前缀,是否由重复出现的子串A组成,即 AAA...A (A重复出现K次,K 大于 1)。如果存在,请找出最短的循环节对应的K值(也就是这个前缀串的所有可能重复节中,最大的K值)。

算法

我们看到这个前缀 自然想到了KMP中的next数组(请不要在实际使用中使用next关键字)
如果一个字符串S是由一个字符串T重复K次形成的,则称T是S的循环元。使K最大的字符串T称为S的最小循环元,此时的K称为最大循环次数。
现在给定一个长度为N的字符串S,对S的每一个前缀S[1~i],如果它的最大循环次数大于1,则输出该前缀的最小循环元长度和最大循环次数。

当i-next[i]能整除i时,S[1i-next[i]]就是S[1i]的最小循环元。它的最大循环次数就是i/(i-next[i])。
进一步地,如果i-next[next[i]]能整除i,那么S[1i-next[next[i]]]就是S[1i]的次小循环元。
以此类推我们还可以找出S[1~i]所有可能的循环元。

代码

01
#include<vector>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<string>
#include<cstdio>
#include<iostream>
using namespace std;
const int maxn = 1000000 + 10;
char s[maxn];
int f[maxn];
int main()
{
    int n,cas=1;
    while(scanf("%d",&n)==1&&n){
        scanf("%s",s);
        //求fail数组
        f[0]=f[1]=0;
        for(int i=1;i<n;i++){
            int j=f[i];
            while(j&&s[i]!=s[j]) j=f[j];
            f[i+1]=(s[i]==s[j])?j+1:0;
        }
        printf("Test case #%d
",cas++);
        for(int i=2;i<=n;i++){
            int length=i-f[i]; //循环节长度
            if(f[i]>0&&i%length==0) printf("%d %d
",i,i/length);
        }
        printf("
");
    }
    return 0;
}

原文地址:https://www.cnblogs.com/dixiao/p/13747214.html