【HDU1686】Oulipo 思路+解题报告+代码+KMP算法个人理解 【0.5%达成】

HDU的题意就是,给你一个字符串A,一个字符串B,求A在B中总共出现了几次,注意,重复的也算。

比如说

str1 = "ABA"

str2 = "ABABABA"

这样的话,那么str1就在str2中出现了三次。

当然,按照HDU一贯淫荡的套路,朴素算法肯定会超时。

Thanks to lpp学长那个秒杀级的样例。。。

——————————注意以下内容纯属个人理解如果有误【十分欢迎,极度渴望】批评指正————————

朴素算法的时间复杂度是O(mn),其中m=10^4,n=10^6,m*n=10^10,必然会超时,至于朴素算法的时间复杂度证明,可以见CLRS(《算法导论》,黑话)中的证明,简单易懂。

我们用KMP算法的话,时间复杂度是O(m+n)=10^6+10^4 约等于 10^6,没关系。

首先我们介绍KMP算法。为了不耽误各位下(哔)片的时间和流量,这里关于KMP算法是怎么来的忽略不算了。KMP算法弥补了朴素算法的缺陷

首先,说一下next[i],也就是各大算法书中由于不知道出于什么心理(可能是把人家弄迷糊?)而叫做“失配函数”的东西,是干嘛的。

关于失配函数的计算。

假设我们有str2要与str1串匹配,那么str2我们就将其叫做模式串。

假设模式串是ABCABDABCABE

那么我们就要找到一个next[i],使得str2[i]满足以下条件

str2[0..next[i]]与str2[i-next[i]..i](str2[0..N]表示str2串中第0位到第N位形成的字符串)相同。

如果不存在这样的i,那么next[i]=-1.

如果习惯了术语说法的话,那么next[i]的值要保证str2[i-next[i]..i]是str2[0..next[i]]的最长公共前缀

str2[]:

A  B  C  A  B  D  A  B  C  A  B  E

next[]:

-1 -1 -1 0  1 -1  0  1  2  3  4  -1

注意next[]中加粗的部分,【为什么这部分不是3,4】,自己想一下吧~(还记得吗?str[0..next[i])与str2[i-next[i]..i]的最长公共前缀)

由此,我们得到了计算next[]的算法:

void initNext()
{
    int len = strlen(word);
    next[0] = -1;
    for(int j = 1 ; j < len ; j++)
    {
        int i = next[j-1];

        while(  (i>=0) && ( word[i+1] != word[j] ) )
        {
            i = next[i];
        }
        if( word[i+1] == word[j] )
        {
            next[j] = i+1;
        }
        else
            next[j] = -1;
    }
    /*
    for(int j = 1 ; j < len ; j++)
    {
        printf("%d ",next[j]);
    } */
}

在上述代码中:

        int i = next[j-1];  //这一行是得到str2[j]的前一个字符的next[]值

        while(  (i>=0) && ( word[i+1] != word[j] ) )
        {
            i = next[i]; /// 当i>=0并且str[ next[j-1]+1 ] 不相等的时候,i不断向前回溯,直到i=-1或者word[i+1]=word[j],可以手动求一下ABCDABCEABCF~有惊喜
        }牢记next[i]的作用是让str2[0..next[i]]与str2[i-next[i]..i]相等!
        if( word[i+1] == word[j] )  //这个就好理解吧
        {
            next[j] = i+1;
        }
        else
            next[j] = -1;

这样就计算出了next[],由于for循环最多也就加str2len次,时间复杂度为O(m),m为str2的长度。


那么,KMP算法比较时候,按照这道题,应该是

int solve()
{
    int cnt = 0;
    int i = 0 ;
    int j = 0 ;
    int lenp = strlen(word);
    int lens = strlen(text);

    while( i <= lens  )
    {
        if( i == lens )
        {
            if( j == lenp )
                cnt++;
            break;   //防止a串是ABC,b串是ABC的情况
        }
        if( text[i] == word[j] )
        {
            i++;j++;
        }
        else
        {
            if( j == 0 )
                i++;  //如果不相同且j=0,那么说明第一位就不匹配,j-1=-1,就越界了,此时i++就可以了
            else
                j = next[j-1]+1;  //如果相同,那么与next[j-1]+1匹配,也就是与str2[]开头的第next[j-1]+1匹配
//                                  因为str2[0..next[j-1]]与str2[j-next[j-1]-1,j-1]完全相同
        
        if( j >= lenp )  //好吧这里才是解题的关键嗯,如果j>=lenp(attern),那么就说明找到了j匹配,为了保证重复的串也被计算,那么我们要与next[lenp-1]+1处比较,因为str2[0..next[lenp-1]与str2[lenp-1-next[lenp-1],lenp-1]完全相同。
        {
            cnt++;
            j = next[lenp-1]+1;
        }
    }
    return cnt;
}

完整AC代码如下:

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#define ONLINE_JUDGE
using namespace std;

const int MAX_SIZE_1 = 10010;
const int MAX_SIZE_2 = 1000010;
char word[MAX_SIZE_1];
char text[MAX_SIZE_2];

int next[MAX_SIZE_1];

void initNext()
{
    int len = strlen(word);
    next[0] = -1;
    for(int j = 1 ; j < len ; j++)
    {
        int i = next[j-1];

        while(  (i>=0) && ( word[i+1] != word[j] ) )
        {
            i = next[i];
        }
        if( word[i+1] == word[j] )
        {
            next[j] = i+1;
        }
        else
            next[j] = -1;
    }
    /*
    for(int j = 1 ; j < len ; j++)
    {
        printf("%d ",next[j]);
    } */
}
int solve()
{
    int cnt = 0;
    int i = 0 ;
    int j = 0 ;
    int lenp = strlen(word);
    int lens = strlen(text);

    while( i <= lens  )
    {
        if( i == lens )
        {
            if( j == lenp )
                cnt++;
            break;
        }
        if( text[i] == word[j] )
        {
            i++;j++;
        }
        else
        {
            if( j == 0 )
                i++;
            else
                j = next[j-1]+1;
        }
        if( j >= lenp )
        {
            cnt++;
            j = next[lenp-1]+1;
        }
    }
    return cnt;
}
int main()
{
#ifndef ONLINE_JUDGE
    freopen("B:\\acm\\SummerVacation\\String-I\\A.in","r",stdin);
    freopen("B:\\acm\\SummerVacation\\String-I\\A.out","w",stdout);
#endif
    int T;
    while(scanf("%d\n",&T)!=EOF)
    {
        for(int t = 0 ; t < T ; t++)
        {
            memset(word,0,sizeof(word));
            memset(text,0,sizeof(text));
            memset(next,0,sizeof(next));


            gets(word);
            gets(text);

            initNext();
            int ans = 0;

            ans = solve();

            printf("%d\n",ans);
        }
    }
#ifndef ONLINE_JUDGE
    fclose(stdin);
    fclose(stdout);
#endif
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值