KMP解决字符串最小循环节相关问题

经典问题 : 给出一个由某个循环节构成的字符串,要你找出最小的循环节,例如 abababab 最小循环节当是 ab ,而类似 abab 也可以成为它的循环节,但并非最短。

分析 :

对于上述问题有两个结论

如果对于next数组中的 i, 符合 i % ( i - next[i] ) == 0 && next[i] != 0 , 则说明字符串循环,而且

循环节长度为:    i - next[i]

循环次数为:       i / ( i - next[i] )

水平有限,用自己的语言描述怕有差错,给出一个参考博客  ==>  http://www.cnblogs.com/jackge/archive/2013/01/05/2846006.html

再抛一个问题 : 有没有想过对于一个不完整的循环串要补充多少个才能使得其完整?

答案是==>(循环节长度) - len%(循环节长度) 即 (len - next[len]) - len%(len - next[len])

为什么? (以下胡扯,看不懂就掠过吧.........)

首先考虑整串就是循环节构成的情况,类似 abcxabcx 观察构造出来的next值显然满足上式,得出答案 0

那现在考虑不完整的情况,例如 abcabca 、其 next 值为 -1 0 0 0 1 2 3 4 。现在考虑末尾的 a,若没有它,而是将 c 作为末尾则会在 len 失配的时候会回溯道下一个循环节的末尾即 abca , 那现在多了一个a,那么回溯当然也应该是(循环节长度 + 1) 即 abcab,故 len 那里无论是否刚好为循环节的末尾,只是个"残"的末尾,未圆满的循环节,len-next[len]也是循环节长度,那需要补多少个呢?现在就很显然了!下面相关题目的 ① 就是这样的一个问题。

相关题目 :

HDU 3746 Cyclic Nacklace

题意 : 给出一个字符串,问你最少补充多少个字母才能使得字符串由两个或者以上的循环节构成

分析 : 由结论可知,如果字符串循环,那么最小循环节的长度为 len - next[len] ,并且这个字符串总长能被循环节长度整除说明字符串已经循环,否则 len % (len - next[len]) 则为多出来的部分,例如 abcabcab ==> len - next[len] = 3,而 len % 3 == 2 很明显就是余出来两个,这两个应当是循环节的头两个字母,对于其他串也可以自己模拟看看,所以需要补充的就是 循环节长度 - 多余出来的长度

#include<stdio.h>
#include<string.h>
using namespace std;
const int maxn = 1e5 + 10;
char mo[maxn];
int Next[maxn], moL, nCase;
inline void GetNext()
{
    int i = 0, j = -1;
    Next[i] = j;
    while(i < moL){
        while( j!=-1 && mo[i]!=mo[j]) j = Next[j];
        Next[++i] = ++j;
    }
}
int ans()
{
    GetNext();
    if(Next[moL] == 0) return moL;

    int Period_len = moL - Next[moL];
    int Remain = moL % Period_len;

    if(Remain == 0) return 0;
    return Period_len - Remain;
}
int main(void)
{
    scanf("%d", &nCase);
    while(nCase--){
        scanf("%s", mo);
        moL = strlen(mo);
        printf("%d\n", ans());
    }
    return 0;
}

POJ 1961 Period

题意 : 给出一个字符串,叫你给出这个字符串存在的不同循环节长度以及个数 ( 循环节构成的不一定是整个字符串,也有可能是其子串 )

分析 : 根据以上的结论,我们只要让构造出字符串的next数组,而后一个for循环判断当前长度和当前最小循环节长度是否是倍数关系,即 i % ( i - next[i] ) == 0 && next[i] != 0,就能判断是否为一个循环节了,循环节的长度自然是 i / (i-next[i])

#include<stdio.h>
using namespace std;
const int maxn = 1e6 + 10;
char mo[maxn];
int Next[maxn], moL;
inline void GetNext()
{
    int i = 0, j = -1;
    Next[i] = j;
    while(i < moL){
        while( j!=-1 && mo[j]!=mo[i] ) j = Next[j];
        Next[++i] = ++j;
    }
}
inline void PrintAns()
{
    GetNext();
    int Period;
    for(int i=1; i<=moL; i++){
        if(Next[i] != 0){
            Period = i - Next[i];
            if(i % Period == 0){
                printf("%d %d\n", i, i/Period);
            }
        }
    }puts("");
}
int main(void)
{
    int Case = 1;
    while(~scanf("%d", &moL) && moL){
        scanf("%s", mo);
        printf("Test case #%d\n", Case++);
        PrintAns();
    }
    return 0;
}

HUST 1010  The Minimum Length

题意 : 假设 A 是一个循环字符串,现在截取 A 的某一段子串 B 出来,给出 B 问你构成 A 的循环节的最小长度是多少?

分析 : 既然是循环串当中截取出来的,那么只要根据结论公式算出最小循环节长度即是答案,可以证明证明这样做永远是最优的。以下代码由于HUST OJ崩了,所以不知道结果

#include<stdio.h>
#include<string.h>
#include<iostream>
using namespace std;
const int maxn = 1e6 + 10;
int Next[maxn], moL;
char mo[maxn];
inline void GetNext()
{
    int i = 0, j = -1;
    Next[i] = j;
    while(i < moL){
        while(j!=-1 && mo[i]!=mo[j]) j = Next[j];
        Next[++i] = ++j;
    }
}
int Ans()
{
    GetNext();
    if(Next[moL] == 0) return moL;
    else return moL - Next[moL];
}
int main(void)
{
    while(~scanf("%s", mo)){
        moL = strlen(mo);
        printf("%d\n", Ans());
    }
    return 0;
}

POJ 2406 Power String

题意 : 给你一个字符串,问你它由多少个相同的字符串拼接而成

分析 : 直接找算出最小循环节长度,如果字符循环,则答案为 len / (循环节长度) ,而对于 len % (循环节长度) != 0 和 next[len] == 0 的情况答案就是 1 了

#include<string.h>
#include<stdio.h>
using namespace std;
const int maxn = 1e6 + 10;
int Next[maxn], moL;
char mo[maxn];
inline void GetNext()
{
    int i = 0, j = -1;
    Next[i] = j;
    while(i < moL){
        while(j!=-1 && mo[i]!=mo[j]) j = Next[j];
        Next[++i] = ++j;
    }
}
int Ans()
{
    GetNext();
    if(Next[moL] == 0) return 1;
    int Period = moL - Next[moL];
    if(moL % Period != 0) return 1;
    return moL / Period;
}
int main(void)
{
    while(scanf("%s", mo) && mo[0]!=‘.‘){
        moL = strlen(mo);
        printf("%d\n", Ans());
    }
    return 0;
}

POJ 2752 Seek the Name, Seek the Fame

题意 : 给出一个字符串,问你所有关于这个字符串的前缀和后缀相同的长度,比如 abcab 有 1 "a"、2 "ab"、5 "abcab"

分析 : 这里就要巧妙利用到 next 数组的性质了,根据next数组定义可以知道 next[len] 表示一个从头开始长度为 next[len] 的前缀和相同长度的后缀相等,那么next[ next[len] ]呢?next[ next[ next[len] ] ]呢?这里的一层层嵌套实际上都是一个长度为 next[ next[len] ] 或者 长度 next[ next[ next[len] ] ]的前缀和后缀相等,自己构造个数组画画图也能得出来这个规律,那么到此,这个问题是不是被圆满的解决了呢!

#include<string.h>
#include<stack>
#include<stdio.h>
using namespace std;
const int maxn = 4e5 + 10;
char mo[maxn];
int Next[maxn], moL;
inline void GetNext()
{
    int i = 0, j = -1;
    Next[i] = j;
    while(i < moL){
        while(j!=-1 && mo[j]!=mo[i]) j = Next[j];
        Next[++i] = ++j;
    }
}
inline void PrintAns()
{
    moL = strlen(mo);
    GetNext();
    int tmp = Next[moL];
    stack<int> ans;///根据题目要求需要递增输出长度,而我们得出的答案顺序正好相反,所以利用栈存储
    while(tmp != -1){///直到头为止
        ans.push(tmp);
        tmp = Next[tmp];
    }
    while(!ans.empty()){
        int Top = ans.top(); ans.pop();
        if(Top) printf("%d ", Top);
    }
    printf("%d\n", moL);
}
int main(void)
{
    while(~scanf("%s", mo)){ PrintAns(); }
    return 0;
}

时间: 2024-11-04 11:15:46

KMP解决字符串最小循环节相关问题的相关文章

D - Cyclic Nacklace HDU3746 (kmp 计算字符串最小循环节 )

D - Cyclic Nacklace Time Limit:1000MS Memory Limit:32768KB 64bit IO Format:%I64d & %I64u Submit Status Description CC always becomes very depressed at the end of this month, he has checked his credit card yesterday, without any surprise, there are on

KMP + 求最小循环节 --- POJ 2406 Power Strings

Power Strings Problem's Link: http://poj.org/problem?id=2406 Mean: 给你一个字符串,让你求这个字符串最多能够被表示成最小循环节重复多少次得到. analyse: KMP之next数组的运用.裸的求最小循环节. Time complexity: O(N) Source code:  /** this code is made by crazyacking* Verdict: Accepted* Submission Date: 20

(KMP 1.4)hdu 3746 Cyclic Nacklace(使用next数组来求循环节的长度——求一个字符串需要添加多少个字符才能使该字符串的循环节的个数&gt;=2)

题目: Cyclic Nacklace Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 3387    Accepted Submission(s): 1549 Problem Description CC always becomes very depressed at the end of this month, he has che

hdu1358 KMP求字符串最小循环节

对于一个字符串S,长度为L,如果由长度为len的字符串s(字符串s的最小循环节是其本身)循环k次构成,那么字符串s就是字符串S的最小循环节 那么字符串有个很重要的性质和KMP挂钩,即  i - next[i] 为字符串s的长度 i%(i - next[i]) ==0 证明:字符串S由s循环k次构成,那么有S[0-->L-len-1] == S[len-->L-1],即前k-1个循环节和后k-1个循环节构成的字符串相等 那么此时KMP数组的next[L] = k-1个循环节的长度, 也即 nex

poj 1961 Period【求前缀的长度,以及其中最小循环节的循环次数】

Period Time Limit: 3000MS   Memory Limit: 30000K Total Submissions: 14653   Accepted: 6965 Description For each prefix of a given string S with N characters (each character has an ASCII code between 97 and 126, inclusive), we want to know whether the

Uvalive - 3026 Period (kmp求字符串的最小循环节+最大重复次数)

参考:http://www.cnblogs.com/jackge/archive/2013/01/05/2846006.html 总结一下,如果对于next数组中的 i, 符合 i % ( i - next[i] ) == 0 && next[i] != 0 , 则说明字符串循环,而且 循环节长度为:   i - next[i] 循环次数为:       i / ( i - next[i] ) 1 #include <iostream> 2 #include <cstdi

(KMP 1.5)hdu 1358 Period(使用next数组来求最小循环节——求到第i个字符的循环节数)

题目: Period Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submission(s): 3813    Accepted Submission(s): 1862 Problem Description For each prefix of a given string S with N characters (each character has an A

[coj 1353 Guessing the Number]kmp,字符串最小表示法

题意:给一个字符串,求它的最小子串,使得原串是通过它重复得到的字符串的一个子串. 思路:先求最小长度,最小循环长度可以利用kmp的next数组快速得到,求出长度后然后利用字符串最小表示法求循环节的最小表示即可. #pragma comment(linker, "/STACK:10240000") #include <map> #include <set> #include <cmath> #include <ctime> #include

Poj1961--Period(Kmp, Next数组求循环节长度 &amp;&amp; 出现次数)

Period Time Limit: 3000MS   Memory Limit: 30000K Total Submissions: 14657   Accepted: 6967 Description For each prefix of a given string S with N characters (each character has an ASCII code between 97 and 126, inclusive), we want to know whether the