KMP初学

时间限制:1000ms

单点时限:1000ms

内存限制:256MB

描述

小Hi和小Ho是一对好朋友,出生在信息化社会的他们对编程产生了莫大的兴趣,他们约定好互相帮助,在编程的学习道路上一同前进。

这一天,他们遇到了一只河蟹,于是河蟹就向小Hi和小Ho提出了那个经典的问题:“小Hi和小Ho,你们能不能够判断一段文字(原串)里面是不是存在那么一些……特殊……的文字(模式串)?

小Hi和小Ho仔细思考了一下,觉得只能想到很简单的做法,但是又觉得既然河蟹先生这么说了,就肯定不会这么容易的让他们回答了,于是他们只能说道:“抱歉,河蟹先生,我们只能想到时间复杂度为(文本长度 * 特殊文字总长度)的方法,即对于每个模式串分开判断,然后依次枚举起始位置并检查是否能够匹配,但是这不是您想要的方法是吧?”

河蟹点了点头,说道:”看来你们的水平还有待提高,这样吧,如果我说只有一个特殊文字,你能不能做到呢?“

小Ho这时候还有点晕晕乎乎的,但是小Hi很快开口道:”我知道!这就是一个很经典的模式匹配问题!可以使用KMP算法进行求解!“

河蟹满意的点了点头,对小Hi说道:”既然你知道就好办了,你去把小Ho教会,下周我有重要的任务交给你们!“

”保证完成任务!”小Hi点头道。

提示一:KMP的思路

提示二:NEXT数组的使用

提示三:如何求解NEXT数组

输入

第一行一个整数N,表示测试数据组数。

接下来的N*2行,每两行表示一个测试数据。在每一个测试数据中,第一行为模式串,由不超过10^4个大写字母组成,第二行为原串,由不超过10^6个大写字母组成。

其中N<=20

输出

对于每一个测试数据,按照它们在输入中出现的顺序输出一行Ans,表示模式串在原串中出现的次数。

×Close

提示一:KMP的思路

小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数组。

Close

×Close

提示二: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笑道:“但是让我先喝口水!”

Close

×Close

提示三:如何求解NEXT数组

“首先我们不想如何求整个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建议道。

”好!“

Close

样例输入
5
HA
HAHAHA
WQN
WQN
ADA
ADADADA
BABABB
BABABABABABABABABB
DAD
ADDAADAADDAAADAAD
样例输出
3
1
3
1
0

#include <stdio.h>
#include <string.h>
int count=0;
char s[10005],l[1000005];
int next[10005];
void getnext(char *src, int m, int *next){
	int i=0,j=-1;
	next[0]=j;
	while(i<m){
		while(j!=-1 && src[j] != src[i])
			j=next[j];
		i++,j++;
		if(j>=m)
			next[i]=next[j-1];
		else
			next[i]=j;
	}
}
void kmp(char *s, int m, char *l, int n)
{
	int next[10005];
	getnext(s,m,next);
	int i=0, j=0;
	while(i<n){
		while(j!=-1 && s[j] != l[i]){
			j = next[j];
		}
		i++,j++;
		if(j==m){
			count++;
			j=next[j];
		}
	}
}
int main()
{
	int t;
	scanf("%d",&t);
	while (t--)
	{
		count=0;
		scanf("%s%s",&s,&l);
        //getnext(s,strlen(s),next);
		kmp(s,strlen(s),l,strlen(l));
		/*for (int i=0;i<strlen(s);i++)
            printf("%d ",next[i]);*/
		printf("%d\n",count);
	}
	return 0;
}
时间: 2024-10-27 06:11:37

KMP初学的相关文章

// codeforces 471D // kmp初学

// codeforces 471D // #include<cstdio> #include<cstring> #include<cstdlib> #include<cmath> #include<iostream> #include<algorithm> using namespace std; int n,w; int a[200005],b[200005]; int da[200005],db[200005]; int nex

KMP算法变形——对链表的处理

KMP算法简单粗暴的代码,严密的逻辑,初学的时候,真的很难搞懂,不过曾力胜老师这周出的模式串匹配的变形题目,让我反思了一下KMP算法,昨天晚上写出了链表形式,也算是进步吧.昨天太急,没来得及记录,今天补充起来. /* Name: KMP之链表写法 Date :2015/3/29 Write by:杨领 */ #include<stdio.h> #include<stdlib.h> typedef struct node { int data;//数据域 struct node *n

UVa 1328 (KMP求字符串周期) Period

当初学KMP的时候也做过这道题,现在看来还是刘汝佳的代码要精简一些,毕竟代码越短越好记,越不容易出错. 而且KMP的递推失配函数的代码风格和后面的Aho-Corasick自动机求失配函数的代码风格也是一致的. 1 #include <cstdio> 2 3 const int maxn = 1000000 + 10; 4 char s[maxn]; 5 int f[maxn]; //失配函数 6 7 int main() 8 { 9 //freopen("in.txt",

数据结构实验——KMP算法之Test.ming

翻译程序 小明初学C++,已明白了四则运算.关系运算.逻辑运算.赋值运算.输入输出.简单选择和循环结构的用法,但他的英语不太好,记不住太多的保留字,于是他利用汉语拼音做保留字,山寨C++,发明了一种表达自己思想的算法描述规则. 规则很简单:他将开始程序头部以一个拼音名字标记,C++程序中的"{,}"用拼音"kaishi,jieshu"直观表示:选择和循环只采用一种单一的结构,且保留字也分别用对应的拼音表示,不过在表示选择或循环条件时他去掉了多余的小括号:输入输出也用

Educational Codeforces Round 21 G. Anthem of Berland(dp+kmp)

题目链接:Educational Codeforces Round 21 G. Anthem of Berland 题意: 给你两个字符串,第一个字符串包含问号,问号可以变成任意字符串. 问你第一个字符串最多包含多少个第二个字符串. 题解: 考虑dp[i][j],表示当前考虑到第一个串的第i位,已经匹配到第二个字符串的第j位. 这样的话复杂度为26*n*m*O(fail). fail可以用kmp进行预处理,将26个字母全部处理出来,这样复杂度就变成了26*n*m. 状态转移看代码(就是一个kmp

hiho 1015 KMP算法 &amp;&amp; CF 625 B. War of the Corporations

#1015 : KMP算法 时间限制:1000ms 单点时限:1000ms 内存限制:256MB 描述 小Hi和小Ho是一对好朋友,出生在信息化社会的他们对编程产生了莫大的兴趣,他们约定好互相帮助,在编程的学习道路上一同前进. 这一天,他们遇到了一只河蟹,于是河蟹就向小Hi和小Ho提出了那个经典的问题:“小Hi和小Ho,你们能不能够判断一段文字(原串)里面是不是存在那么一些……特殊……的文字(模式串)?” 小Hi和小Ho仔细思考了一下,觉得只能想到很简单的做法,但是又觉得既然河蟹先生这么说了,就

hdu2328 Corporate Identity 扩展KMP

Beside other services, ACM helps companies to clearly state their "corporate identity", which includes company logo but also other signs, like trademarks. One of such companies is Internet Building Masters (IBM), which has recently asked ACM for

KMP算法详解

这几天学习kmp算法,解决字符串的匹配问题,开始的时候都是用到BF算法,(BF(Brute Force)算法是普通的模式匹配算法,BF算法的思想就是将目标串S的第一个字符与模式串T的第一个字符进行匹配,若相等,则继续比较S的第二个字符和 T的第二个字符;若不相等,则比较S的第二个字符和T的第一个字符,依次比较下去,直到得出最后的匹配结果.BF算法是一种蛮力算法.)虽然也能解决一些问题,但是这是常规思路,在内存大,数据量小,时间长的情况下,还能解决一些问题,但是如果遇到一些限制时间和内存的字符串问

2016——3——16 kmp习题

1.传送门:http://begin.lydsy.com/JudgeOnline/problem.php?id=2725 题目大意:找一个串在另一个串中出现的次数 题解:kmp(纯裸题) 2.传送门:http://begin.lydsy.com/JudgeOnline/problem.php?id=2732 题目大意:给你一个字符串,让你求出最大重复周期(最大周期不和本身重合) 题解:kmp或者扩展kmp(但我并未用这种方法),我用的是kmp,但是一直WA,原来是求next数组把while写成了