CodeVS1404 字符串匹配 扩展kmp讲解
题目描述 Description
给你两个串A,B,可以得到从A的任意位开始的子串和B匹配的长度。
给定K个询问,对于每个询问给定一个x,求出匹配长度恰为x的位置有多少个。
N,M,K<=200000
输入描述 Input Description
第一行三个数 N,M,K,表示A的长度、B的长度和询问数。
第二行为串A。
第三行为串B。
接下来K行,每行1个数X。
输出描述 Output Description
对于每个询问输出一个数。
样例输入 Sample Input
6 2 2
aabcde
ab
0
2
样例输出 Sample Output
4
1
数据范围及提示 Data Size & Hint
各个测试点1s
分析
假如有匹配串A长度为N,模式串B长度为M,那么扩展KMP算法可以在O(N+M)的时间内算出对于A的每一个位置,与B的最长匹配长度是多少(即与B串前缀重合的最长长度),算法如下:
PART_1 初始化next数组
设next[i]表示B串的i位置开始的字符串与B串的前缀的最长重合长度(注意这里与KMP中的next是不一样的)。明显地,next[1]=M(我的字符串的下标习惯从1开始),next[2]也可以用一个简单的for循环求出,pos初始化为2;当i∈[3,M]时,我首先认为关于1~i-1的信息已全部求出,我们记录一个pos,它的意义是当前已计算出的next数组中,j+next[j]-1能达到的最右端所对应的i,这个不太好理解,可以参考manacher算法的思想戳这(其实manacher和扩展kmp是很像的),令rp=pos+next[pos]-1
(1)当i+next[i-pos+1]<rp时,易得next[i]=next[i-pos+1]
(2)当i+next[i-pos+1]>=rp时,rp后的元素,即b[rp+j],可能会和b[rp-i+1+j]相等,这时进行暴力扩展,如果扩展发生,则更新pos为i,next[i]直接在扩展中求出。
注意:有时rp可能小于i,这时可以加一特判,直接进行暴力求
PART_2 匹配
令ans[i]表示a串中以a[i]为开头的后缀和b串的最长匹配长度,设pos表示a串中对于所有的i,i+ans[i]-1能达到的最远位置对应的i,rp就是pos+ans[pos]-1,next[1]暴力求出,pos初始化为1;当i∈[2,N]时:
(1)若i+next[i-pos+1]-1<rp,则ans[i]=next[i-pos+1]
(2)若i+next[i-pos+1]-1>=rp,则进行暴力扩展,同时更新pos,ans在扩展时求出
注意:如果rp<i,那么加一特判,直接暴力扫描
代码
//CodeVS1404 字符串匹配 扩展KMP
#include <cstdio>
#include <cstring>
#include <algorithm>
#define maxlen 200010
using namespace std;
char a[maxlen], b[maxlen];
int next[maxlen], N, M, K, cnt[maxlen], ans[maxlen], tong[maxlen];
void init()
{
int i, j, p, a;
next[1]=M;
for(i=1;b[i]==b[i+1];i++);
next[2]=i-1;
a=2;
for(i=3;i<=M;i++)
{
p=a+next[a]-1;
if(i+next[i-a+1]-1>=p)
{
if(p<i)
{
for(j=i;b[j]==b[j-i+1];j++);j--;
if(b[j]==b[j-i+1]&&j>=i)next[i]=j-i+1;
}
else
{
for(j=1;b[p+j]==b[p-i+1+j];j++);
next[i]=p-i+j;
}
if(i+next[i]-1>p)a=i;
}
else next[i]=next[i-a+1];
}
}
void exkmp()
{
int i, j, rp, pos;
for(i=1;i<=min(N,M)&&a[i]==b[i];i++);i--;
ans[1]=i;
tong[i]++;
pos=1;
for(i=2;i<=N;i++)
{
rp=pos+ans[pos]-1;
if(i+next[i-pos+1]-1>=rp)
{
if(rp<i)
{
for(j=i;a[j]==b[j-i+1];j++);j--;
if(a[j]==b[j-i+1]&&j>=i)ans[i]=j-i+1;
}
else
{
for(j=rp+1;a[j]==b[j-i+1];j++);j--;
ans[i]=j-i+1;
}
}
else ans[i]=next[i-pos+1];
if(i+ans[i]-1>rp)pos=i;
tong[ans[i]]++;
}
}
int main()
{
int q;
scanf("%d%d%d",&N,&M,&K);
scanf("%s%s",a+1,b+1);
init();
exkmp();
while(K--)
{
scanf("%d",&q);
printf("%d\n",tong[q]);
}
return 0;
}