hdu 2087 剪花布条(KMP入门)
Problem Description
一块花布条,里面有些图案,另有一块直接可用的小饰条,里面也有一些图案。对于给定的花布条和小饰条,计算一下能从花布条中尽可能剪出几块小饰条来呢?
Input
输入中含有一些数据,分别是成对出现的花布条和小饰条,其布条都是用可见ASCII字符表示的,可见的ASCII字符有多少个,布条的花纹也有多少种花样。花纹条和小饰条不会超过1000个字符长。如果遇见#字符,则不再进行工作。
Output
输出能从花纹布中剪出的最多小饰条个数,如果一块都没有,那就老老实实输出0,每个结果之间应换行。
Sample Input
abcde a3
aaaaaa aa
#
Sample Output
0
3
解题思路:这是一道KMP入门题,设计出这个算法的人真的太聪明了,字符串匹配过程真的太巧妙了!!!这道题就是给你一个主串和一个模式串,要求从主串找出没有交集的模式串的最大数量。一开始很容易想到暴力枚举,不过会超时,时间复杂度是O(nm),还不如花多点时间学习新的算法思想,争取在做题中灵活应用。对于KMP的讲解,目前有一篇大牛写的算是賊好理解了,这个算法时间复杂度是O(n+m),传送门:KMP算法最浅显理解——一看就明白
AC代码一:
1 #include<bits/stdc++.h> 2 using namespace std; 3 char text[1005],pattern[1005];//主串,模式串,问主串中能截取出多少个模式串,取出后再从后面的子串中查找 4 int prefix[1005],lena,lenb,num; //prefix[i]记录的是前i-1个字符构成的子串中最长公共前后缀长度 5 void get_prefix_table(){//处理模式串的前缀表 6 int j=0,pos=-1; 7 prefix[0]=-1;//prefix[0]初始化为-1,-1表示不存在相同的最大前缀和最大后缀,即模式串第一个字符前面不存在子串 8 while(j<lenb){ 9 if(pos==-1||pattern[pos]==pattern[j])prefix[++j]=++pos;//当pos为-1时,++j,++pos表示下一个字符前的子串的最长公共前后缀长度为0 10 else pos=prefix[pos];//如果当前j指向的字符和pos指向的字符不同,则pos退回到pos指向下标为当前pos位置前面子串的最长公共前后缀长度的字符,再次进行比较 11 } 12 } 13 void kmp_search(){//从主串中能截取出多少模式串,不叠加计算 14 int i=0,j=0;//i从主串下标为0开始,j从模式串下标为0开始 15 while(i<lena){ 16 if(j==-1||text[i]==pattern[j])i++,j++;//如果j==-1,说明主串中i位置前面的子串与模式串没有交集,即前者的后缀和后者的前缀不相等,则同时右移一位 17 else j=prefix[j];//j退回到j指向下标为模式串中当前j位置前面子串的最长公共前后缀长度的字符,再次进行比较 18 if(j==lenb)num++,j=0;//如果j等于模式串的长度,则计数器加1,并且j重置为0,再寻找主串中后面的子串是否还包含模式串 19 } 20 } 21 int main(){ 22 while(~scanf("%s",text)&&strcmp(text,"#")){ 23 scanf("%s",pattern); 24 memset(prefix,0,sizeof(prefix)); 25 num=0,lena=strlen(text),lenb=strlen(pattern); 26 get_prefix_table(); 27 kmp_search(); 28 printf("%d\n",num); 29 } 30 return 0; 31 }
代码过程简单分析:如上所示,我们先对模式串进行打印前缀表,然后再调用kmp_search()来与主串匹配。这里给一串模式串abaabcac计算其前缀表。首先我们定义prefix[0]=-1,表示模式串第1个字符前面的子串的最长公共前后缀长度不存在,定义为-1,待循环模式串到j,如果第j个字符pattern[j]!=pattern[pos],pos退回到下标为第pos个字符前面的子串的最长公共前后缀长度,再次比较,如果继续不等,pos将会退回到-1,这时满足if语句则执行prefix[++j]==0,表示下一个字符前面的子串的最长公共前后缀长度为0,为什么是先计算下一个字符的prefix[j]呢?这是为了代码好写,简洁一些,巧妙处理前缀表,所以给出模式串的前缀表如下:
模式串的下标 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[j]记录的是前j-1个字符组成的子串的最长前后缀公共长度。举个栗子:仍用上面的模式串,假设现在我们要求最后的'c'字符下的prefix[8],怎么求呢?已知prefix[7]=1,说明'c'字符前面的字符串abaabca其最长公共前后缀长度为1,这时我们加上了字符'c',那么只需比较最前面的第1个字符'a'后面字符'b'是否等于当前字符'c',即判断pattern[j=1]==pattern[i=7]?如果相等的话就执行prefix[7+1]=prefix[8]=1+1=2(表示最后一个字符的下一个位置前面的子串的最长公共前后缀长度是2),否则j=prefix[j=1]=0,很明显,pattern[j=0]!=pattern[i=7],所以j继续退回j=prefix[j=0]=-1,这时说明字符'c'已不能与第一个字符'a'前面的字符进行匹配,满足if语句,即prefix[8]=-1+1=0。接下来进行模式串与主串的匹配,其匹配模式和计算前缀表所用的方法基本一样,多了一步判断j(j是从0开始计算的)是否达到模式串尾,即判断j==lenb?如果是,计数器加1,并且j重置为0,即从模式串第一个字符开始,再在主串中剩下的子串中查找模式串。
解法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 *p,text[1005],pattern[1005];int len,num; 4 int main(){ 5 while(cin>>text&&strcmp(text,"#")){ 6 cin>>pattern; 7 num=0,len=strlen(pattern); 8 for(p=text;(p=strstr(p,pattern))!=NULL;num++,p+=len); 9 cout<<num<<endl; 10 } 11 return 0; 12 }