题解报告:hdu 2087 剪花布条(KMP入门)

Posted 霜雪千年

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了题解报告:hdu 2087 剪花布条(KMP入门)相关的知识,希望对你有一定的参考价值。

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2087

Problem Description
一块花布条,里面有些图案,另有一块直接可用的小饰条,里面也有一些图案。对于给定的花布条和小饰条,计算一下能从花布条中尽可能剪出几块小饰条来呢?
Input
输入中含有一些数据,分别是成对出现的花布条和小饰条,其布条都是用可见ASCII字符表示的,可见的ASCII字符有多少个,布条的花纹也有多少种花样。花纹条和小饰条不会超过1000个字符长。如果遇见#字符,则不再进行工作。
Output
输出能从花纹布中剪出的最多小饰条个数,如果一块都没有,那就老老实实输出0,每个结果之间应换行。
Sample Input
abcde a3
aaaaaa aa
#
Sample Output
0
3
解题思路:这是一道KMP入门题,设计出这个算法的人真的太聪明了,菜鸡佩服得五体投地,字符串匹配过程真的太巧妙了!!!这道题就是给你一个主串和一个模式串,要求从主串找出没有交集的相同模式串的最大数量。一开始很容易想到暴力枚举,不过会超时,时间复杂度是O(nm),况且题目给出的字符串长度不超过10^3,最坏的时候是10^6基本算是超时的了。所以还不如花多点时间学习新的算法思想,争取在做题中灵活应用。对于KMP的讲解,目前有一篇大牛写的算是賊好理解了,这里贴一下大神之作(菜鸡仰慕):这个算法时间复杂度是O(n+m),真的是太强大了~ 

KMP算法最浅显理解——一看就明白

 AC代码:解法一:
 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 char text[1005],pattern[1005];//主串,模式串
 5 int perfix[1005],lena,lenb,num;  //prefix数组存放的是最长公共前后缀长度
 6 
 7 void KMP()  //KMP匹配字符串算法
 8 {
 9     int i=0,j=-1;//预设j初始化为-1
10     perfix[0]=-1;//prefix[0]初始化为-1,-1表示不存在相同的最大前缀和最大后缀
11     while(i<lenb){  //先计算模式串的最长公共前后缀长度,打印前缀表
12         if(j==-1||pattern[i]==pattern[j])perfix[++i]=++j;  //当为-1的时候,文本串和模式串同时i++,j++
13         //j==-1表示若主串的第i个字符和模式的第1个字符不等,应从主串的第i+1个字符开始重新进行匹配
14         else j=perfix[j];  //向右移动模式串
15     }
16     i=j=0;// i是循环主串的指针位置,j是更新模式串当前指针位置,首先将j置为0,将模式串的第一个字符与主串开始比较
17     while(i<lena){    //主串与模式串的匹配
18         if(j==-1||pattern[j]==text[i]){i++;j++;}  //若是j为-1或是主串与模式串当前字符相等,同时i++,j++
19         else j=perfix[j];  //不满足条件,调整模式串的位置,表现为右移
20         if(j==lenb){num++;j=0;}//求不重复的循环次数从0开始(初始值)求重叠式的为 j=perfix[j]
21     }
22 }
23 
24 int main()
25 {
26     while(cin>>text){  //a为文本串,b为模式串
27         if(strcmp(text,"#")==0)break;
28         cin>>pattern;
29         num=0,lena=strlen(text),lenb=strlen(pattern);//num是用来计数主串包含多少个模式串
30         KMP();  //KMP算法
31         cout<<num<<endl;
32     }
33     return 0;
34 }

代码过程简单分析:如上所示,我们先对模式串进行打印前缀表,然后再应用KMP算法来与主串匹配。这里给一串模式串abaabcac计算其前缀表。首先我们将前缀表prefix[0]=-1是前导出-1,待循环匹配到i时如果前面没有最长公共前后缀,j==-1加1后变为当前字符串prefix[i]==0,巧妙处理了前缀表。所以给出字符串的前缀表如下:
模式串的下标          0  1  2  3  4  5  6  7

模式串               a  b  a  a  b  c  a  c

前缀表下标         0  1  2  3  4  5  6  7  8

最长前后缀公共长度 -1  0  0  1  1  2  0  1  0

注意上面前缀表与模式串对应的下标位置。计算好模式串的前缀表之后,开始与主串进行匹配了,用i,j分别指向主串,模式串的当前位置,当匹配失败是,指针i(指向主串)不变,指针j(指向模式串)退回到prefix[j]所指示的位置上重新进行比较,并且当指针j退至-1时,指针i和指针j需同时增1。即若主串的第i个字符和模式串的第j个字符不等,应从主串的第i+1个字符重新进行匹配。

上面给的大牛博客里讲的很清楚了,为什么j要退回到prefix[j],这里谈谈我的学习心得:prefix数组记录的是当前字符与前面组成的字符串的最长前后缀公共长度。举个栗子:仍用上面的模式串,假设现在我们要求最后的‘c‘字符下的prefix[8],怎么求呢?已知prefix[7]=1,说明‘c‘字符前面的字符串abaabcac其最长前后缀公共长度为1,这时我们加上字符‘c‘,只需比较最前面的第1个字符‘a‘后面字符‘b‘是否等于当前字符‘c‘,如果相等的话就执行prefix[7+1]=1+1=2(表示最后一个字符下与前面的字符串组成的最大公共前后缀长度是2),否则继续退回,直到-1,显然这里是不等的,所以j会退回-1,这时说明字符‘c‘没办法与第一个字符‘a‘匹配,即prefix[8]=0,退回这个算法很巧妙,不过也在情理之中。接下来进行模式串与主串的匹配,其算法和计算前缀表差不多,多了一步判断j(j是从1开始计算的)是否达到模式串尾,即j==lenb?这样就判断主串是否包含了模式串,同时用num进行计数主串中含有多少个不相交的模式串,OK,满满的收获!

解法2:这道题要求找主串中含有的模式串,那么可以运用库函数strstr()来计数num。

strstr 语法:头文件#include <string.h>

char *strstr( const char *str1, const char *str2 );

功能:函数返回一个指针,它指向字符串str2 首次出现于字符串str1中的位置,如果没有找到,返回NULL。

解题思路:通过这个函数的特点,因为返回的是地址,所以我们定义一个字符指针p来指向接受返回地址,如果找到的话,加上模式串的长度,即接下去寻找是否含有模式串,如果找不到的话,返回NULL将会退出当前循环。

AC代码:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 char text[1005],pattern[1005];//主串,模式串
 4 int main()
 5 {
 6     int len,num;
 7     char *p;
 8     while(cin>>text){
 9         if(strcmp(text,"#")==0)break;
10         cin>>pattern;
11         num=0,len=strlen(pattern);
12         for(p=text;p=strstr(p,pattern);num++,p+=len);
13         cout<<num<<endl;
14     }
15     return 0;
16 }

 

以上是关于题解报告:hdu 2087 剪花布条(KMP入门)的主要内容,如果未能解决你的问题,请参考以下文章

hdu 2087 剪花布条(KMP)

hdu2087 剪花布条kmp

HDU2087 剪花布条KMP

HDU2087 剪花布条(KMP)

hdu2087剪花布条(KMP)

HDU 2087 剪花布条 (简单KMP或者暴力)