题目1:KMP算法
题目原文:http://hihocoder.com/contest/hiho3/problem/1
【题目解读】
就是KMP算法的基本使用之一。添加了计数功能。
KMP算法的核心思想,就是通过减少匹配失败时模式串的回溯位数,来减少匹配次数。此时回溯的时候,使用 next 数组。
求解 next 数组见:http://www.cnblogs.com/dolphin0520/archive/2011/08/24/2151846.html,需要注意的是,这里用到了数学归纳法。即:
假设:根据定义next[0]=-1。然后,假设next[j]=k, 即P[0...k-1]==P[j-k,j-1]
则有:1)若P[j]==P[k],则有P[0..k]==P[j-k,j],很显然,next[j+1]=next[j]+1=k+1;
2)若P[j]!=P[k],则可以把其看做模式匹配的问题,即匹配失败的时候,k值如何移动,显然k=next[k]
next 数组的求解,可以视为相错一位的模式串,其中一个作为原始串,另外一个作为模式串,进行匹配,失败则回溯
【编写细节】
(1)想要理解KMP算法的主体比较部分,建议先实现 BF算法,就是最普通的暴力匹配。如下:
#include<stdio.h> #include<stdlib.h> #include<string.h> char ori[1000005]; char par[10005]; int next[10005]; int main() { int t; scanf("%d", &t); long olen; int plen; int ans; while(t--) { ans = 0; memset(ori, 0, sizeof(ori)); memset(par, 0, sizeof(par)); memset(next, 0, sizeof(next)); scanf("%s", par); scanf("%s", ori); olen = strlen(ori); plen = strlen(par); for(long i=0; i<olen; i++) { long j = i; int k = 0; while(ori[j] == par[k]) { j++; k++; if(k >= plen) break; } if(k>=plen) ans++; } printf("%d\n", ans); } return 0; }
(2)KMP里面如果不匹配就回溯(绝不用--),一律使用k = next[k],不会使用到 k--;只要回溯了第一次,之后不匹配即可不断回溯直至找到匹配或者直接到头;
(3)一定要注意!KMP算法需要单独考虑模式串与原始串长度均为1时的情况!次数 next 数组只有一个值-1;
(4)此题是KMP匹配的计数功能,所以需要原始串的一位一位字符扫描(for循环),以防遗漏ABABA中有两个ABA的情况;
(5)其余细节写在代码注释。。。很难搞,当模板记下来吧。
【AC代码】
#include<stdio.h> #include<stdlib.h> #include<string.h> char ori[1000005]; char par[10005]; int next[10005]; int t, plen; long olen, ans; void get_next(int len) { next[0] = -1; int j = 0; int k = -1; while(j < len) { // 一直找到可以匹配的开始,或者无法匹配的开头 if(k==-1 || par[j] == par[k]) { // 此时保证j不能已经加过 next[j+1] = k + 1; j++; k++; } else { k = next[k]; } } } int main() { scanf("%d", &t); while(t--) { ans = 0; scanf("%s", par); scanf("%s", ori); plen = strlen(par); olen = strlen(ori); if(olen==1 && plen==1) { if(ori[0] == par[0]) ans = 1; else ans = 0; printf("%d\n", ans); continue; } get_next(plen); int j = 0; for(long i=0; i<olen; i++) { // while的顺序:一定要先找到一个可以匹配的起点 // j>=1杜绝出现next[j]=-1,因此单独考虑 olen=plen=1 while(ori[i] != par[j] && j>=1) { j = next[j]; } if(ori[i] == par[j]) { j++; } // 在找到匹配的第一个字符前,i不能继续++ // else // { // j = next[j]; // } if(j >= plen) { ans++; // 回溯过程中不会遗漏,最差情况从头匹配 j = next[j]; } } printf("%d\n", ans); } return 0; }