说句闲话:题解管理员大大的审核速度天下第一
题目链接
题目大意:
给定两个字符串,要求第二个字符串在第一个字符串内类似的出现过多少次
Solution:
这道题是字符串查找的强化版,其实搞懂了那道题这道题就很简单了。
我们首先来看什么情况下两个字符串类似,其实要满足三个条件:
- 两个字符串 \(s1,s2\) 长度相等。
- 当 \(s1_{i}\) 为大写字母时,\(s2_{i}\) 也必须为相同的大写字母。
- 当 \(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;
}
再说一句,这道题有加强版,可以当作双倍经验做做。