P11705 「KTSC 2020 R1」字符串查找 个人题解
题解区咋没人啊,蒟蒻被 \(26\) 个队列吓到了,来补发一篇简单 KMP 的题解
题目大意
给定两个字符串,求第二个字符串在第一个字符串内的子串里多少个是实际上相同的。
Solution
实际上相同是指第一个字符串的字串与第二个字符串的字符在相对位置上一致,比如题目样例中的 aba 和 pqp 在相对位置上一致,与原本字母无关,这就告诉我们不能直接用 KMP 来找,而是需要一种转换以后再来做。
我们发现如果两字符串的排列样式一致,那么这两个字符串实际上相同,其实就是找每个字符的位置关系一不一样。怎么记录位置关系呢,我们可以用一个 \(pre_{i}\) 数组来表示上一个相同的字符出现的位置,用 \(a_{i}\) 数组来表示现在这一个字符到上一个出现的字符的距离,就可以表示位置关系啦,比如样例中的 abababbab,对应的 \(a\) 数组就是 \(0,0,2,2,2,2,1,3,2\),所以问题就转化为第二个字符串的 \(a\) 数组在第一个字符串的 \(a\) 数组内能匹配几次。
但是我们发现,以上的 \(a\) 数组是把第一个数的距离设为了 \(0\),导致样例中第二个字符串的 \(a\) 数组为 \(0,0,2\),与第一个字符串的无法匹配,这就需要给这些第一次出现的字符设置一个“通配符”,即什么都配的上(因为此时相当于单个字符),我们在这里用其当前位置作为其通配符(下见代码)
好了,现在我们处理完了 \(a\) 数组了,我们要求第二个字符串的 \(a\) 数组在第一个字符串的 \(a\) 数组中出现的次数,一眼 KMP(不会 KMP 的可以看看这篇题解的前半段,写的非常详细),但是还没完,直接套板子是不对的,因为我们给 \(a\) 数组加入了通配符,而在做 KMP 的时候这些通配符是不应该处理到 \(next\) 数组的,所以在比较 \(a_{i}\) 与 \(b_{j+1}\) 以及 \(b_{i}\) 与 \(b_{j+1}\) 的时候要单拿出来考虑是否为通配符,怎么考虑呢,其实也挺简单,只要保证不是第一次出现就可以,我们设计一个 compare 函数:
inline bool compare(int a,int b,int len){
return (a==b) || (a>len && b>len);
}
其中 \(len\) 是当前匹配的长度,要想保证不是第一次出现,只要都大于 \(len\) 就好了。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int nxt[N],a[N],b[N],pre[N],ans;
inline bool solve(int a,int b,int len){//判断有无通配符存在
return (a==b) || (a>len && b>len);
}
int findP(char t[], char p[], int n, int m){
for(int i=n;i>=1;i--)//个人习惯下标从1开始
t[i]=t[i-1];
for(int i=m;i>=1;i--)
p[i]=p[i-1];
for(int i=1;i<=n;i++){//分别处理a数组和pre数组
a[i]=i-pre[t[i]-'a'+1];
pre[t[i]-'a'+1]=i;
}
memset(pre,0,sizeof(pre));
for(int i=1;i<=m;i++){
b[i]=i-pre[p[i]-'a'+1];
pre[p[i]-'a'+1]=i;
}
for(int i=2,j=0;i<=m;i++){//KMP
while(j>0 && !solve(b[j+1],b[i],j))
j=nxt[j];
if(solve(b[j+1],b[i],j))
j++;
nxt[i]=j;
}
for(int i=1,j=0;i<=n;i++){
while(j>0 && !solve(b[j+1],a[i],j))
j=nxt[j];
if(solve(b[j+1],a[i],j))
j++;
if(j==m){
ans++;
j=nxt[j];
}
}
return ans;
}
注:双倍经验,这个还有大小写区分,但本质上不变。

浙公网安备 33010602011771号