您的位置:首页 > 其它

poj 2406 poj 1961 个人对吉大KMP模板的理解 KMP 基础题--找周期串

2013-08-27 12:49 513 查看
好佩服写kmp代码的人...   看死了终于看得有点明白了......


学kmp先看两个比较好的总结

一是大牛matrix67的  http://www.matrix67.com/blog/archives/115/

另一个是 http://www.cppblog.com/oosky/archive/2006/07/06/9486.html

这两个讲解非常好

摘一句我认为最重要的话

预处理出这样一个数组P[j],表示当匹配到B数组的第j个字母而第j+1个字母不能匹配了时,新的j最大是多少。P[j]应该是所有满足B[1..P[j]]=B[j-P[j]+1..j]的最大值。

(B数组就是模式串)
额,似乎难以理解,那我就摘抄下matrix67的话吧:

假如,A="abababaababacb",B="ababacb",我们来看看KMP 是怎么工作的。我们用两个指针i和j分别表示,A[i-j+ 1..i]与B[1..j]完全相等。也就是说,i是不断增加的,随着i的增加j相应地变化,且j满足以A[i]结尾的长度为j的字符串正好匹配B串的前 j个字符(j当然越大越好),现在需要检验A[i+1]和B[j+1]的关系。当A[i+1]=B[j+1]时,i和j各加一;什么时候j=m了,我们就 说B是A的子串(B串已经整完了),并且可以根据这时的i值算出匹配的位置。当A[i+1]<>B[j+1],KMP的策略是调整j的位置
(减小j值)使得A[i-j+1..i]与B[1..j]保持匹配且新的B[j+1]恰好与A[i+1]匹配(从而使得i和j能继续增加)。我们看一看当 i=j=5时的情况。

    i = 1 2 3 4 5 6 7 8 9 ……

    A = a b a b a b a a b a b …

    B = a b a b a c b

    j = 1 2 3 4 56 7

    此时,A[6]<>B[6]。这表明,此时j不能等于5了,我们要把j改成比它小的值j'。j'可能是多少呢?仔细想一下,我们发现,j'必须 要使得B[1..j]中的头j'个字母和末j'个字母完全相等(这样j变成了j'后才能继续保持i和j的性质)。这个j'当然要越大越好。在这里,B[1..5]="ababa",头3个字母和末3个字母都是"aba"。而当新的j为3时,A[6]恰好和B[4]相等。于是,i变成了6,而j则变成了 4:

    i = 1 2 3 4 5 6 7 8 9 ……

    A = a b a b a b a a b a b …

    B =     a b a b a cb

    j =     1 2 3 4 5 67

    从上面的这个例子,我们可以看到,新的j可以取多少与i无关,只与B串有关。我们完全可以预处理出这样一个数组P[j],表示当匹配到B数组的第j个字母而第j+1个字母不能匹配了时,新的j最大是多少。P[j]应该是所有满足B[1..P[j]]=B[j-P[j]+1..j]的最大值。

    再后来,A[7]=B[5],i和j又各增加1。这时,又出现了A[i+1]<>B[j+1]的情况:

    i = 1 2 3 4 56 7 8 9 ……

    A = a b a b ab a a b a b …

    B =     a b a b a cb

    j =     1 2 3 4 5 67

    由于P[5]=3,因此新的j=3:

    i = 1 2 3 4 56 7 8 9 ……

    A = a b a b ab a a b a b …

    B =         a b a ba c b

    j =         1 2 3 45 6 7

    这时,新的j=3仍然不能满足A[i+1]=B[j+1],此时我们再次减小j值,将j再次更新为P[3]:

    i = 1 2 3 4 56 7 8 9 ……

    A = a b a b ab a a b a b …

    B =             a ba b a c b

    j =             1 23 4 5 6 7

    现在,i还是7,j已经变成1了。而此时A[8]居然仍然不等于B[j+1]。这样,j必须减小到P[1],即0:

    i = 1 2 3 4 56 7 8 9 ……

    A = a b a b ab a a b a b …

    B =               a b a b a c b

    j =             0 12 3 4 5 6 7

    终于,A[8]=B[1],i变为8,j为1。事实上,有可能j到了0仍然不能满足A[i+1]=B[j+1](比如A[8]="d"时)。因此,准确的说法是,当j=0了时,我们增加i值但忽略j直到出现A[i]=B[1]为止。

我用的是吉大的代码(似乎写法跟主流不太一样?):

int fail[P];

int kmp(char* str, char* pat)
{
int i, j, k;
memset(fail, -1, sizeof(fail));
for(i = 1; pat[i]; ++i)
{
for(k=fail[i-1]; k>=0 && pat[i]!=pat[k+1];k=fail[k]);
if(pat[k + 1] == pat[i]) fail[i] = k + 1;
}
i = j = 0;
while( str[i] && pat[j] )
{// By Fandywang
if( pat[j] == str[i] ) ++i, ++j;
else if(j == 0)++i;//第一个字符匹配失败,从str下个字符开始
else j = fail[j-1]+1;
}
if( pat[j] )return -1;
else return i-j;
}


说了这么多,全是抄的别人的...现在该谈我自己的理解了:

KMP代码做了两件事:一是处理模式串(有用pat,pattern,B表示)整理出fail数组(也有说next数组,matrix67说的是P);
二是从原串中查找模式串;

poj 2406 poj 1961 都是仅仅需要理解一就行。先看poj 2406  http://poj.org/problem?id=2406

重复一下那句最重要的话:预处理出这样一个数组P[j],表示当匹配到B数组的第j个字母而第j+1个字母不能匹配了时,新的j最大是多少。P[j]应该是所有满足B[1..P[j]]=B[j-P[j]+1..j]的最大值。 

如fail[i]=k;则从pat[0]到pat[i]一共i+1个字符,fail[i]=k意味着从fail[0]到fail[i-1]这i个字符0到k-1这前k个字符形成的子串与i-k到i-1这k个字符形成的子串相同,如abcdabcd,fail[5]=1,就是“abcda”,pat[0]==pat[4],所以是1,

这个例子跟poj 2406还不是很接近,那么再看一个例子:

abcabcabc   它的fail[8]=5,前5个字符为"abcab",后5个字符为“abcab”,但因为下标从0开始,并且原串就是由一个子串重复多次得到的,所以可以断言,pat[8]==pat[5],就是说前6个字符和后六个字符相同,再看看,原串长度9-6刚好等于一个周期    pe=len-fail[len-1]-1;那么周期数就是len/pe;

贴代码吧:

//141MS
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 1000003

int fail
;
char str
;

void KMP()
{
int len=strlen(str);
int i=0,k;

memset(fail,-1,sizeof(fail));
/*
k=-1;
while(i<len)
{
if(k==-1||str[i]==str[k])
fail[++i]=++k;
else
k=fail[k];
}
i=len-k;//如果最后一个位置不匹配,那么就会滚到len-k的位置,也就是最小重复字串的长度。
if(len%i==0)
return len/i;
else
return 1;
*/
for(i=1;str[i];i++)
{
for(k=fail[i-1];k>=0&&str[k+1]!=str[i];k=fail[k]);
if(str[k+1]==str[i])fail[i]=k+1;
}
int pe=len-fail[len-1]-1;
if(len%pe==0)printf("%d\n",len/pe);
else printf("1\n");
}

int main()
{
while(scanf("%s",str)!=EOF)
{
if(!strcmp(str,"."))break;
KMP();
}
return 0;
}


注释掉的是另一种写法...懒得去学了....

知道这个题再去看poj 1961 就很容易做了;

题意是,前缀如果是周期串,找出它由几个周期组成,典型的poj 2406翻版,不过是检测原串的从0到i形成的字串是不是周期串(i<=len-1)

贴代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 1000003

int fail
,len;
char str
;

void kmp()
{
int i,k;

memset(fail,-1,sizeof(fail));
for(i=1;i<len+1;i++)
{
for(k=fail[i-1];k>=0&&str[k+1]!=str[i];k=fail[k]);
if(str[k+1]==str[i])fail[i]=k+1;
int t=fail[i];
if((i+1)%(i-t)==0&&(i+1)/(i-t)>1)printf("%d %d\n",i+1,(i+1)/(i-t));
}
}

int main()
{
//freopen("in.txt","r",stdin);
int ncase=1;

while(scanf("%d",&len),len)
{
printf("Test case #%d\n",ncase++);
scanf("%s",str);
kmp();
putchar('\n');
}

return 0;
}


如果还是不明白kmp,建议模拟,多打印fail数组,k的值,看规律,依据那句最重要的话理解就好
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: