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

好佩服写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[N];
char str[N];

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[N],len;
char str[N];

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的值,看规律,依据那句最重要的话理解就好


  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值