KMP的最小循环节的使用以及推导理解
1.首先引入我的KMP模板:KMP模板
KMP的最小循环节是根据KMP的next[]数组来确定的,所以我们在这里只需要讨论next[]数组就行了。
引入KMP最小循环节、循环周期的结论:
结论:假设S的长度为len,则S存在最小循环节,循环节的长度L为len-next[len],子串为S[0…len-next[len]-1]。
(1)如果len可以被len - next[len]整除,则表明字符串S可以完全由循环节循环组成,循环周期T=len/L。
(2)如果不能,说明还需要再添加几个字母才能补全。需要补的个数是循环个数L-len%L=L-(len-L)%L=L-next[len]%L,L=len-next[len]。
根据上面的结论,结合自身理解:
- 情况1
我们只考虑字符串S完全由循环节组成,并且周期大于1**(在这里我们只讨论周期大于1的原因是字符串 S:abcd 这种情况,它也是完全由循环节组成的,但是周期为1,我们将它与不完全是由最小循环节组成的字符串一起讨论。)好啦,接下来我们只需要看字符串S完全是由最小循环节且周期大于1的情况,如:abcd abcd abcd ,aaaa,abcabc…等。
根据情况1,不妨设它有最小循环节且周期大于1,又next[] 是最长公共前后缀,当满足next[len]>0时,根据情况1(存在数组是完全由最小循环节构成的,在这里那么kmp[len]必定会>0,因为它在最后肯定是由循环节组成肯定存在公共缀>0),所以next[len]的性质不变(即是真正意义上的是最长公共前后缀)
例如: abcd abcd abcd
满足完全是由最小循环节组成的(即由3个abcd组成,abcd即为最小循环节)前面之所以说它性质不变(即为最长公共前后缀)是因为它的next[]数组值在最后一个字符的位置最大,next[len]=8。
在此,还可以推导出:如果字符串完全是由最小循环节组成的并且周期大于1,那么next[len]的值必定大于等于整个字符串S的一半(即next[len]>=len/2)例如,aaaa:next[len]=3>=2。 abc abc :next[len]=3>=3 。abab :next[len]=2>=2 等等。当然这是我自己推导出来的,不具有权威性也不知道严不严谨QAQ。之所以推出next[len]大于等于整个串的一半的长度(当然只存在于情况1中),是为了增强next[len]这里最长公共前后缀的最长这一性质
根据上面结论 :最小循环节 L=len-next[len] .
因为next[len]为最长前后缀的长度,又len为固定的长度,那么在KMP中L肯定是最小的了,刚好满足最小循环节这一性质。(因为next的值是前缀和后缀相等的最大长度,即len-L是最大的,那么在len已经确定的情况下,L是最小的)。 好了,这就是我对情况1的全部理解了,下面来理解情况2.
- 情况2.
如果循环节的周期为1,例如abc,ab,abdd等情况最小循环节的周期都为1.即整个字符串都是由一个循环节构成的。
还可能存在有多个循环节组成,但是还缺少几个字母才能够构成循环节。abdabdab len:8 next[8]:5
最小循环节长度:3(即abd) 需要补的个数是1 d
ababa len:5 next[5]:3
最小循环节长度:2(即ab) 需要补的个数是1 b
所以综上:循环节的长度L=len-next[len]
(1)如果len可以被len - next[len]整除,则表明字符串S可以完全由循环节循环组成,循环周期T=len/L。
(2)如果不能,说明还需要再添加几个字母才能补全,那么最小循环节的长度都为1,即自身即为最小循环节。(即如果next[len]为0的话必定不存在循环周期大于1,如果len不能整除L的话也不能说明它的周期大于1)
假设s可以由t重复k次拼成,即s=tttt……tt,我们称为s=tk。先给定一个字符串s,求最大的n使得存在t满足s=tn。
Input
多组数据,每行一个字符串(仅包含可打印字符且长度不超过1000000),以单独一行.作为终结标志Output
每组数据一行答案
Sample Input
abcd
aaaa
ababab
.
Sample Output
1
4
3
#include <iostream>
#include <string.h>
#include <cstdio>
using namespace std;
const int N=1e6+10;
int kmp[N];
char a[N];
int main()
{
while(scanf("%s",a+1)&&a[1]!='.')
{
int len=strlen(a+1);
for(int j=0,i=2; i<=len; i++)
{
while(j&&a[j+1]!=a[i]) //如果不匹配并且没有越界就一直向前跳到最长公共前后缀的位置
j=kmp[j];
if(a[j+1]==a[i]) //如果匹配了那么长度就要++
j++;
kmp[i]=j; //赋值当前公共前后缀的值
}
int L=len-kmp[len]; //最小循环节L=len-kum[len]
if(kmp[len]==0||len%L!=0)
//最后一个字符的kmp[len]可能是只有一个循环节且等于整个字符串(如:ab)或者可能是由多组循环节组成但是还缺少了几个字符的情况如(abcdab,需要补齐cd)
cout <<1<<"\n"; //这两种情况他们的最小循环节就是字符串本身,即周期为1
else cout <<len/L<<"\n"; //如果字符串全是有最小循环节构成的话那么就直接可以得出周期
}
}
周期
一个字符串的前缀是从第一个字符开始的连续若干个字符,例如 abaab 共有 5 个前缀,分别是 a,ab,aba,abaa,abaab。
我们希望知道一个 N 位字符串 S 的前缀是否具有循环节。
换言之,对于每一个从头开始的长度为 i(i>1)的前缀,是否由重复出现的子串 A 组成,即 AAA…A (A 重复出现 K 次,K>1)。
如果存在,请找出最短的循环节对应的 K 值(也就是这个前缀串的所有可能重复节中,最大的 K 值)。
#include <iostream>
using namespace std;
const int N=1e6+10;
int kmp[N];
char a[N];
int main()
{
int n;
int t=1;
while(scanf("%d",&n),n)
{
cin>>a+1;
cout << "Test case #" << t++ << endl;
for(int j=0,i=2;i<=n;i++)
{
while(j&&a[j+1]!=a[i])
j=kmp[j];
if(a[j+1]==a[i])
j++;
kmp[i]=j;
}
for(int i=2;i<=n;i++) //这里利用kmp数组对前i个字符判断循环节,因为1就只有本身不算
{
int L=i-kmp[i];
if(kmp[i]!=0&&i%L==0)
{ //如果kmp[i]==0那么就说明循环节就是串本身如果不能整除的话就是没有整数个循环节
printf("%d %d\n",i,i/L); //输出循环节的下标和周期数
}
}
cout <<endl;
}
}