《算法竞赛进阶指南》匹配统计

匹配统计

阿轩在纸上写了两个字符串,分别记为A和B。

利用在数据结构与算法课上学到的知识,他很容易地求出了“字符串A从任意位置开始的后缀子串”与“字符串B”匹配的长度。

不过阿轩是一个勤学好问的同学,他向你提出了Q个问题:

在每个问题中,他给定你一个整数x,请你告诉他有多少个位置,满足“字符串A从该位置开始的后缀子串”与B匹配的长度恰好为x。

例如:A=aabcde,B=ab,则A有aabcde、abcde、bcde、cde、de、e这6个后缀子串,它们与B=ab的匹配长度分别是1、2、0、0、0、0。

因此A有4个位置与B的匹配长度恰好为0,有1个位置的匹配长度恰好为1,有1个位置的匹配长度恰好为2。

输入格式
第一行输入三个整数N,M,Q,分别表示A串长度、B串长度、问题个数。

第二行输入字符串A,第三行输入字符串B。

接下来Q行每行输入1个整数x,表示一个问题。

输出格式
输出共Q行,依次表示每个问题的答案。

数据范围
1≤N,M,Q,x≤200000
输入样例:
6 2 5
aabcde
ab
0
1
2
3
4
输出样例:
4
1
1
0
0

对于本题来说,涉及到字符串的多次匹配问题我们可以用哈希的方法在求取长度的时候,由于长度是连续的,在这其中我们还可以用二分的方法求长度。

当然设计到字符串的匹配我们还可以用KMP算法,以往我们用KMP时是用来计算每一个字符段中前缀和后缀匹配的最大长度。

下面我们在来剖析一下KMP的具体过程:虽然我们在用KMP时是只针对一个字符串,但由于我们的模板中是有i、j两个指针的,所以实际上匹配的还是两个字符串,只不过这两个字符串相同而已。

for(int i=2,j=0;i<=m;i++)
	{
		while(j&&b[j+1]!=b[i])j=nxt[j];
		if(b[j+1]==b[i])j++;
		nxt[i]=j;
	}

我们定义i指针指向的字符串为主字符串,j指向的字符串为副字符串的话,KMP的过程是这样的:主循环是i指针从头遍历到末尾,每次循环结束,j指向的就是1~i位置的字符串的前缀和后缀最大的匹配长度;大家可以想象一下两个数组的遍历过程哈。

在这期间我们最重要的是求出了next数组,而这个next数组是副字符串的next数组。

那么本题我们怎么求出符合提议要求的数据呢;首先我们利用KMP求出副字符串,也就是字符串B的next数组,然后我们就可以在next数组的基础上在利用KMP求主字符串A和副字符串B的匹配长度。

注意题意我们相当于要求每一个主字符串后缀与副字符串的匹配长度;而我们第二次KMP是做匹配用的不在改变next数组;在根据定义可知每次循环我们的j指针指向的长度就是最大的副字符串前缀和主字符串后缀的匹配的长度,我们可以通过f数组记录下来长度为j的数量,每当长度为j时就说明有一个主字符串的后缀数组与副字符串匹配的长度为j(即f[j]++)。

 for (int i = 1, j = 0; i <= n; i ++ )
    {
        while (j && a[i] != b[j + 1]) j = nxt[j];
        if (a[i] == b[j + 1]) j ++ ;
        f[j] ++ ;
    }

上述是本题的主要思想,还有一个细节就是当匹配的长度为j时,那么next[j]是不是也匹配了呢,答案当然时肯定的,由于每次循环结尾都是j长度的加一,所以还要有一下操作:

 for (int i = 1, j = 0; i <= n; i ++ )
   {
       while (j && a[i] != b[j + 1]) j = nxt[j];
       if (a[i] == b[j + 1]) j ++ ;
       f[j] ++ ;
   }

那么这是f[j]的还有就有变换了,它表示的就是所有后缀中只要匹配过数量就加上去,我们时可以直接通过f[x]-f[x+1]求出匹配长度准确为x的后缀的个数。

#include <iostream>
#include <algorithm>
using namespace std;
const int N=200010;
int nxt[N];
char a[N],b[N];
int f[N];
int n,m,q;
int main()
{
	cin>>n>>m>>q;
	cin>>(a+1)>>(b+1);
	//求出需要被配字符串的KMP中的nxt数组
	for(int i=2,j=0;i<=m;i++)
	{
		while(j&&b[j+1]!=b[i])j=nxt[j];
		if(b[j+1]==b[i])j++;
		nxt[i]=j;
	}

	//利用KMP算法匹配a、b两个字符串
	//我们先称a为主匹配、b为副匹配
	//虽然KMP算法常用于一个字符串的nxt数组求取中
	//但它的实质还是两个字符串进行匹配
	//对于一个字符串的KMP来说该字符串即作为主匹配有作为副匹配
	//主匹配的遍历过程是从头到尾遍历一边
	//而副匹配用于和主匹配进行匹配,这个过程不断完善nxt数组,也不断用到nxt数组
	
	//因此我们求出副匹配b的nxt数组之后,就可以再用KMP算法匹配a、b两个字符串
	for(int i=1,j=0;i<=n;i++)
	{//i=1的原因是,在一个字符串的kmp算法中1个字符的nxt数组被定义为0的
     //在两个字符串匹配中,相匹配的长度是可以从1开始的
      while(j&&a[i]!=b[j+1])j=nxt[j];//a数组作为主匹配
      if(a[i]==b[j+1])j++;
      f[j]++;//当前j的长度表示的是a、b两字符串匹配的长度
      //注意此时匹配的长度是b的前缀和某一个a的后缀中前j的字符相匹配的结果
	}

	for(int i=m;i;i--)
		f[nxt[i]]+=f[i];
	//这里的原因是,f[i]表示a、b匹配长度为i的后缀的个数
	//根据KMP定义可知,一个字符串S中,它的nxt,表示最大的前缀后缀的匹配长度
	//但在本字符串中nxt的长度只记录了一次,但实际上是有两次的
	
     while(q--)
     {
     	int x;
     	cin>>x;
     	cout<<f[x]-f[x+1]<<endl;
     }
     return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值