应用范围
对于一个匹配串(S)和单个或多个母串(B)的问题
流程
- 先对匹配串造fail数组和AC自动机中的fail类似,即以失败的节点为右端点的
- 复杂度约为 O(n+m)
void pre(){
fail[1]=0;fail[2]=1;
for(int i=1;i<=m;i++){
int j=fail[i];
while(j&&S[i]!=S[j])j=fali[j];
//此时满足S[i-j+1,i]和S[1,j]相等
fail[i+1]=j+1;
}
}
- 和母串进行匹配
void match(){
for(int i=1,j=1;i<=n;i++){
while(j&&B[i]!=S[j])j=fali[j];
//此时满足B[i-j+1,i]和S[1,j]相等
j++;
if(j=m+1){
//get the ans
j=fail[j];
}
}
}
一些变形
- 对于一些问题相等的判断肯定不会只是数值的相等,可能有一些奇葩的定义
例1
- 我们定义两个字符串相等:是他们的其中的每个字符在该段中的相对大小一一对应,就像下面的例子(串中字符大小属于[1,26])
5 6 2 10 10 7 3 2 9
1 4 4 3 2 1
- 此时我们只需改变判断相等的方法即可,但是一定不能忽略了匹配的流程:一个一个的判断(即当前有一对相同的串,我们只需判断它们的尾端加入后新的串是否相等)
- 我们发现字符大小很小,于是我们可以记录一个关于[1,26]前缀数组即可
bool cmp(int A[M][30],int l1,int r1,int a,int B[M][30],int l2,int r2,int b){
for(int i=1;i<=t;i++){
ta[i]=A[r1][i]-A[l1-1][i]+ta[i-1];
tb[i]=B[r2][i]-B[l2-1][i]+tb[i-1];
}
return ta[a-1]==tb[b-1]&&ta[a]==tb[b];//加入前后都相等
}
int main(){
/**读入,构造前缀和**/
fail[2]=1;
for(int i=2,j;i<=m;i++){
j=fail[i];
while(j&&!cmp(Ss,1,j-1,S[j],Ss,i-j+1,i-1,S[i]))j=fail[j];
fail[i+1]=j+1;
}
for(int i=1,j=1;i<=n;i++){
while(j&&!cmp(Bs,i-j+1,i-1,B[i],Ss,1,j-1,S[j]))j=fail[j];
j++;
if(j==m+1)ans[++ans[0]]=i-m+1,j=fail[j];
}
}
例2
- 对于两端串,如果它们的“样子一样”,我们认为它们相等
a b c x c z z a b c
prvi dr prvi tr tr x
-如图
prvi−>c||dr−>x||tr−>z||x−>a
(唯一对应)
- 方法很简单,我们只需记录每个位置的字符上一次出现的下标,即可判断一个新的点(段尾后一个)在该段中的位置,用它判断即可
bool cmp1(int l1,int r1,int l2,int r2){
int ra=nxts[r1];
int rb=nxts[r2];
if(ra<l1&&rb<l2)return 1;
if(ra==rb)return 1;
return 0;
}
bool cmp2(int l1,int r1,int l2,int r2){
int ra=nxts[r1];
int rb=nxtb[r2];
if(ra<l1&&rb<l2)return 1;
if(ra-l1==rb-l2)return 1;
return 0;
}
int main(){
/**读入&映射&造nxt**/
fail[2]=1;
for(int i=2,j;i<=m;i++){
j=fail[i];
while(j&&!cmp1(1,j,i-j+1,i))j=fail[j];
fail[i+1]=j+1;
}
for(int i=1,j=1;i<=n;i++){
while(j&&!cmp2(1,j,i-j+1,i))j=fail[j];
j++;
if(j==m+1){
printf("%d",i-m+1);
return 0;
}
}
}