1. 程式人生 > >題解報告:hdu 2087 剪花布條(KMP入門)

題解報告:hdu 2087 剪花布條(KMP入門)

不變 indent spa bottom fix href pac transform eight

題目鏈接: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入門)