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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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;
}
View Code

 

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;
}
View Code

 

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;
}
View Code

 

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;
}
View Code

 

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;
}
View Code

 

 

以上是关于KMP解决字符串最小循环节相关问题的主要内容,如果未能解决你的问题,请参考以下文章

HDU-1358 Period 字符串问题 KMP算法 求最小循环节

KMP求最小循环节讲解

UVAlive 3026 KMP 最小循环节

Power Strings (KMP求最小循环节)

hdoj3746(kmp算法的nex数组求最小循环节)

codeforces 825F F. String Compression dp+kmp找字符串的最小循环节