P5256 [JSOI2013] 编程作业 个人题解

说句闲话:题解管理员大大的审核速度天下第一

题目链接

题目大意:

给定两个字符串,要求第二个字符串在第一个字符串内类似的出现过多少次

Solution:

这道题是字符串查找的强化版,其实搞懂了那道题这道题就很简单了。

我们首先来看什么情况下两个字符串类似,其实要满足三个条件:

  1. 两个字符串 \(s1,s2\) 长度相等。
  2. \(s1_{i}\) 为大写字母时,\(s2_{i}\) 也必须为相同的大写字母。
  3. \(s1_{i}=s1_{j}\) 且这两个均为小写字母时必须保证 \(s2_{i}=s2{j}\) 同时也得是小写字母(不过不一定需要相等)。

前两个条件好办,第三个条件就有些许困难了,我们发现其与小写字母是什么无关,而是与小写字母之间的位置关系有关,当位置关系一致时就可以匹配上,那么怎么求位置关系呢,我们用两个数组来处理,一个 \(pre\) 数组来表示上一个相同的字符出现的位置,一个 \(a\) 数组来表示现在这一个字符到上一个相同字符的距离,然后我们就把问题转变为用 \(s2\)\(a\) 数组去匹配 \(s1\)\(a\) 数组能匹配上多少次。但是同那道题一样,如果把第一次出现的数字的 \(a\) 数组设置为 \(0\),就会导致匹配不上,最简单解决这类问题的方法是可以为它们设置一个通配符,即什么也可以匹配的上,在这里我用其第一次出现的位置作为其通配符(其实也可以用设置成 \(-1\) 来处理)。

然后我们看大写字母,这是与那道题不一样的地方,因为如果我们直接存大写字母进去的话会影响 \(a\) 数组,导致小写字母匹配时出错,不过我们可以把大写字母赋成负值,这样在处理时就不会出问题了(原因在下面)

然后变成的这个问题就需要用到 KMP 算法(不懂得可以看看这篇题解的前半段)了,一个用于处理一个字符串在另一个字符串内出现的次数。不过在比较是否相等时要注意不能与通配符比较是否相等,其实只需要比较当前的 \(a\) 数组是否大于当前匹配的长度,我们可以写一个 compare 函数来比较:

inline bool compare(int a,int b,int len){
       return (a==b) || (a>len && b>len);
}

那么为什么把大写字母设为负值就不会出问题呢,我们发现大写字母不会与小写字母去比较,且不会进入判断是否有通配符的语句,它只是在跑普通的 KMP 罢了,所以通过赋负值的方法我们解决了大小写区分的问题了。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
inline int read(){
	int x=0,f=1;
	char c=getchar();
	while(c<'0' || c>'9'){
		if(c=='-')
			f=-1;
		c=getchar();
	}
	while(c>='0' && c<='9'){
		x=x*10+c-'0';
		c=getchar();
	}
	return x*f;
}
int Q=read(),nxt[N],pre[N],aa[N],bb[N];
char a[N],b[N];
inline bool compare(int a,int b,int len){//比较是否相等同时判断有无通配符 
	return (a==b) || (a>len && b>len);
}
int main(){
	while(Q--){
		int ans=0;
		scanf("%s%s",a+1,b+1);
		int n=strlen(a+1);
		int m=strlen(b+1);
		memset(pre,0,sizeof(pre));
		for(int i=1;i<=n;i++){
			if(a[i]>='A' && a[i]<='Z')//如果是大写字母直接赋负值 
				aa[i]=-(a[i]-'A'+1);
			else{
				aa[i]=i-pre[a[i]-'a'+1];
				pre[a[i]-'a'+1]=i;
			}
		}
		memset(pre,0,sizeof(pre));
		for(int i=1;i<=m;i++){
			if(b[i]>='A' && b[i]<='Z')
				bb[i]=-(b[i]-'A'+1);
			else{
				bb[i]=i-pre[b[i]-'a'+1];
				pre[b[i]-'a'+1]=i;
			}
		}
		nxt[1]=0;
		for(int i=2,j=0;i<=m;i++){
			while(j>0 && !compare(bb[j+1],bb[i],j))
				j=nxt[j];
			if(compare(bb[j+1],bb[i],j))
				j++;
			nxt[i]=j;
		}
		for(int i=1,j=0;i<=n;i++){
			while(j>0 && !compare(bb[j+1],aa[i],j))
				j=nxt[j];
			if(compare(bb[j+1],aa[i],j))
				j++;
			if(j==m){
				ans++;
				j=nxt[j];
			}
		}
		printf("%d\n",ans);
	}
	return 0;
}

再说一句,这道题有加强版,可以当作双倍经验做做。

posted @ 2025-11-19 10:43  See_you_soon  阅读(6)  评论(0)    收藏  举报