1. 程式人生 > >hihocoder 1015 : KMP算法(kmp)

hihocoder 1015 : KMP算法(kmp)

機房 沒有 等於 接下來 sample 想要 desc 大寫字母 復雜

傳送門

Description

小Hi和小Ho是一對好朋友,出生在信息化社會的他們對編程產生了莫大的興趣,他們約定好互相幫助,在編程的學習道路上一同前進。

這一天,他們遇到了一只河蟹,於是河蟹就向小Hi和小Ho提出了那個經典的問題:“小Hi和小Ho,你們能不能夠判斷一段文字(原串)裏面是不是存在那麽一些……特殊……的文字(模式串)?”

小Hi和小Ho仔細思考了一下,覺得只能想到很簡單的做法,但是又覺得既然河蟹先生這麽說了,就肯定不會這麽容易的讓他們回答了,於是他們只能說道:“抱歉,河蟹先生,我們只能想到時間復雜度為(文本長度 * 特殊文字總長度)的方法,即對於每個模式串分開判斷,然後依次枚舉起始位置並檢查是否能夠匹配,但是這不是您想要的方法是吧?”

河蟹點了點頭,說道:”看來你們的水平還有待提高,這樣吧,如果我說只有一個特殊文字,你能不能做到呢?“

小Ho這時候還有點暈暈乎乎的,但是小Hi很快開口道:”我知道!這就是一個很經典的模式匹配問題!可以使用KMP算法進行求解!“

河蟹滿意的點了點頭,對小Hi說道:”既然你知道就好辦了,你去把小Ho教會,下周我有重要的任務交給你們!“

”保證完成任務!”小Hi點頭道。

提示一:KMP的思路

提示二:NEXT數組的使用

提示三:如何求解NEXT數組

Input

第一行一個整數N,表示測試數據組數。

接下來的N*2行,每兩行表示一個測試數據。在每一個測試數據中,第一行為模式串,由不超過10^4個大寫字母組成,第二行為原串,由不超過10^6個大寫字母組成。

其中N<=20

Output

對於每一個測試數據,按照它們在輸入中出現的順序輸出一行Ans,表示模式串在原串中出現的次數。

Sample Input

5
HA
HAHAHA
WQN
WQN
ADA
ADADADA
BABABB
BABABABABABABABABB
DAD
ADDAADAADDAAADAAD

Sample Output

3
1
3
1
0

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6+5;
char a[maxn],b[maxn];
int nxt[maxn];

void pre_kmp(char *a,int n)
{
	int i,k;
	for (nxt[0] = k = -1,i = 1;i < n;nxt[i++] = k)
	{
		while (~k && a[k+1] != a[i])	k = nxt[k];
		if (a[k+1] == a[i])	k++;
	}
}

int kmp(char *a,int n,char *b,int m)
{
	int i,k,res = 0;
	for (k = -1,i = 0;i < m;i++)
	{
		while (~k && a[k+1] != b[i])	k = nxt[k];
		if (a[k+1] == b[i])	k++;
		if (k == n - 1)	res++;
	}
	return res;
}

int main()
{
	//freopen("input.txt","r",stdin);
	int T;
	scanf("%d",&T);
	while (T--)
	{
		scanf("%s %s",a,b);
		int alen = strlen(a);
		int blen = strlen(b);
		pre_kmp(a,alen);
		printf("%d\n",kmp(a,alen,b,blen));
	}
	return 0;
}

 

提示

提示一:

小Hi和小Ho回到了學校,為了完成河蟹托付的偉大使命,小Hi立馬把小Ho抓到了機房開始上課。

“小Ho,你來看看這樣一段原串和模式串~”小Hi說著遞上了一張紙條。

原串: bababababababababb
模式串: bababb

“嗯,這個例子中模式串bababb在原串中第13個字符開始的地方出現了”小Ho看了看,回答道。

“我們假設仍然使用最普通的方法來進行判斷,即我們先枚舉原串中的一個起始位置,然後判斷從這個位置開始的字符串是否能和模式串進行完匹配。”小HI說道,“然後我們來看這個過程中有沒有什麽可以縮減的計算量。”

“好的!”小Ho點點頭。

“你看,在起始點為1的時候,匹配到第6個字符的時候發生了失敗,這個時候我們應當做的是是不是將模式串右移一位,然後從頭開始判斷,就像這樣?”小Hi又在紙上畫了畫,遞給了小Ho。

原串: bababababababababb
模式串: bababb
原串: bababababababababb
模式串: bababb

”是的,然後我們發現第一位就發現不能進行匹配。“小Ho老老實實的回答。

”然後我們再將模式串右移一位,然後再從頭開始判斷,這次我們成功的越過了原串的第7個字符,在第8個字符產生了不同。“小Hi繼續往下推演。

原串: bababababababababb
模式串: bababb

”然後之後的劇情非常的相似,都是要麽最後一個字符匹配不成功,要麽就是第一個字符就匹配不成功,一直到了最後一次機會的時候才匹配成功。“小Ho做了總結。

”那你覺得這個過程中有沒有什麽沒有必要計算的呢?“小Hi於是問道。

”我是這麽認為的,你看這條線。“小Ho在兩個串上對著的一個位置畫了一條線。

原串: babab | ababababababb
模式串: babab | b

”嗯?”

“這是我們第一次產生了字符不匹配的情況,那麽接下來的過程中一定會出現兩種情況之一:一種情況是模式串與原串的對齊點(即枚舉的原串中的起點位置)越過了這條線,仍然沒能匹配成功,而另一種情況是原串中這個位置的字符與模式串中某個位置的字符匹配上了。”小Ho分析道:”我們先不考慮第一種情況,而來看看第二種情況會發生什麽。“

原串: babab | ababababababb
模式串(對齊點=1): babab | b
模式串(對齊點=3): bab | a

”看不出嘛,小Ho你今天變成聰明了嘛!~”小Hi由衷的贊嘆道。

“那當然,畢竟我最近在討論區解答了很多問題,這很鍛煉人的好麽!“小Ho笑嘻嘻的回答道。

”那我也得表現下,接下來換我來說吧,反正你肯定也就差不多想到這麽多是吧!“小Hi也是看破了小Ho的底細,這般說道。於是小Ho點了點頭,讓小Hi接著說。

”我相信一個很容易註意到的事實就在於,如果我用i表示原串和模式串產生分歧的位置(模式串上的位置,註意!這個和對齊點是不一樣的東西,一個在原串上,一個在模式串上),用j表示為了匹配掉位置i上產生分歧的字符而將模式串的對齊點移動到的位置,我們會發現,模式串[1, i-j]的這一段和[j, i - 1]這一段是相同的。比如在這個例子中i=6,j=3,我們會發現模式串[1, 3]和[3,5]是相同的。“小Hi整理了下思路,如是說道。

原串: ba | bab | a babababababb
模式串(i=1): ba | bab | b
模式串(i=3): | bab | a

”而我們同時也會發現,只有在存在一個長度k,使得模式串[1, i-k]和[k, i-1]這兩段相同的情況下,將模式串對其到位置k,才能保證原串和模式串的匹配過程能夠進入到原串的位置i是否和模式串的對應字符相同的判定,在別的情況下,根本都進入不到位置i的判斷就會發生不一致的情況了。”說著小Hi又拋出了另外一個命題。

“我已經開始有點暈了!”小Ho提出了抗議。

“那你就好好的讀一遍我剛才說的話!然後自己在草稿紙上演算一下這個樣例,很快就可以得出結果的!”小Hi如是說道。”總而言之我們現在需要的一個數據是,這個長度k最長是多少,而且我們對於模式串的每一個位置i,都要計算這個值。”而這就是KMP中最為重要的一個點——NEXT數組。

提示二:

“那麽,為了能夠充分理解NEXT數組,我們再來回顧一下如何使用NEXT數組~"小Hi擺出一副老師的樣子,說道。”首先我們來給出NEXT數組的數學定義~“

NEXT[0] = -1
NEXT[i] = max{ 0<=k< i | str.substring(1, k) == str.substring(i - k +1 , i) } 其中str.substring(i, j)表示str從位置i到位置j的子串,如果i>j則,substring為空

”那麽我們對之前例子中的模式串進行求解,可以得到這樣的NEXT數組。“小Hi在紙上寫了又寫,畫了又畫。

模式串: b a b a b b
NEXT: 0 0 1 2 3 1

”然後再來看這個NEXT數組是如何使用的!為了表明NEXT的所有使用情況,我們換一個原串。然後首先,我們第一次匹配,如果用ori表示原串,用par表示模式串,用p表示原串的下標(從1開始),用q表示模式串的下標(從1開始)的話,會發現最多匹配到p=5, q=5就不能往下匹配了,因為此時ori[p +1]不等於par[q + 1]“小Hi為了使說明更加簡潔,先下了一堆定義。

”好的!小Hi老師好棒!“小Ho在一旁煽風點火道。

原串(p=5): babab | abcbababababb
模式串(q=5): babab | b

”此時,令q = NEXT[q],並將ori[1..p]和par[1..q]對齊,便會發現ori[1..p]和par[1..q]仍然是一一對應的。“

原串(p=5): babab | abcbababababb
模式串(q=3): bab | abb

“此時,ori[p+1]和par[q+1]相同了,於是可以繼續往下匹配,但是到了p=7,q=5的時候又發現不能夠接著匹配了。”

原串(p=7): bababab | cbababababb
模式串(q=5): babab | b

”此時,令q = NEXT[q],並將ori[1..p]和par[1..q]對齊,便會發現ori[1..p]和par[1..q]仍然是一一對應的,這和之前是一樣的。”

原串(p=7): bababab | cbababababb
模式串(q=3): bab | abb

“此時,ori[p+1]和par[q+1]仍然不相同,於是還得令q=NEXT[q]。”

原串(p=7): bababab | cbababababb
模式串(q=1): b | ababb

“此時,ori[p+1]和par[q+1]仍然不相同,令q=NEXT[q]。”

原串(p=7): bababab | cbababababb
模式串(q=0): | bababb

“此時,ori[p+1]和par[q+1]仍然不相同,令q=NEXT[q]。”

原串(p=7): bababab | cbababababb
模式串(q=-1): | bababb

”到了這一步,就相當於我們之前所說的模式串與原串的對齊點(即枚舉的原串中的起點位置)越過了這條線(當時指C右側的那條線)的情況,這種情況下,就應當p和q均+1,然後繼續之前的操作。”小Hi擦了一把汗,說道。

“這樣一說,我就大致能夠理解NEXT數組是怎麽用來求解模式匹配問題的了,但是它是如何求的呢?一般的方法不是要O(模式串長度的立方)的麽?”小Ho問道。

“這就是我接下來要和你說的啦!”小Hi笑道:“但是讓我先喝口水!”

提示三:

“首先我們不想如何求整個NEXT數組,而是假設我們已經知道了之前例子中模式串的NEXT[1..4],來求NEXT[5]如何?”小Hi建議道。

“好的!這樣我們就只需要平方級的算法就可以算出它的值了!”小Ho高興道。

“有點追求好不好!”小Hi深深的吸了一口氣:“你這樣和之前的解法有什麽不同麽!”

“似乎沒有。。那你說怎麽算吧!我反正腦子已經成漿糊了。”小Ho郁悶道。

“我們把par.substring(1, 5)當做新的原串ori_new,然後把par.substring(1, 4)當做新的模式串par,會如何?”小Hi微微一笑。

“會。。我來試試!"小Ho接過小Hi手中的紙筆,便開始演算:“首先就直接匹配到了p=4, q=4的情況,這時候嚴格來說已經算匹配完成了,但是肯定不是就這麽結束的,此時par_new[q +1]因為是空字符,所以肯定和ori_new[p+1]匹配不上。於是令q = NEXT[q]”

原串(p=4): baba | b
模式串(q=4): baba |
原串(p=4): baba | b
模式串(q=2): ba | b

”然後這時候ori_new[p + 1]就直接和par_new[q + 1]匹配上了,於是新的p=5,q=3,莫非……這個最後的q就是NEXT[5]!“小Ho忽然靈光一閃。

”沒錯,就是這樣!那你想想現在如何求NEXT[6]。“小Hi繼續引導小Ho。

”首先我們沒有必要重新從頭開始匹配,直接在原串和模式串的後面加上第6個字符就可以了。“小Ho分析道。

原串(p=5): babab | b
模式串(q=3): bab | abb

”沒法繼續匹配,於是令q=NEXT[q]。“

原串(p=5): babab | b
模式串(q=1): b | ababb

”還是沒法繼續匹配,於是令q=NEXT[q]。“

原串(p=5): babab | b
模式串(q=0): | bababb

”此時可以匹配了,新的p=6,q=1,所以NEXT[6]就是1!“小Ho高興道:”沒想到NEXT數組的本身會用一種遞歸的方式進行求解,真是太巧妙了!“

”那你要不要趕緊去寫一下代碼,KMP算法的代碼可是可以寫的很短很巧妙的哦!~“小Hi建議道。

”好!“

hihocoder 1015 : KMP算法(kmp)