【BZOJ 4259】残缺的字符串

本文介绍了一种利用FFT快速傅里叶变换进行字符串匹配的算法,针对包含通配符'*'的模板串和目标串,通过计算特定函数fj来确定所有可能的匹配起始位置。算法首先将模板串反转并扩展,然后通过FFT计算卷积形式的fj值,最后判断fj是否等于0以确定匹配情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

传送门


Problem

给出长度为 m m m 的串 A A A 和长度为 n n n 的串 B B B A A A 为模板串), ∗ * 号表示这个位置可以是任意字母。对于字符串 B B B,求出每个可以与 A A A 匹配的开头位置(下标从 1 1 1 开始)。

数据范围: 1 ≤ m ≤ n ≤ 300000 1\le m\le n\le300000 1mn300000


Solution

这个 ∗ * 限制了我们的字符串算法。。。

假设 B B B 从第 j j j 位开始与 A A A 匹配,定义 f j = ∑ i = 0 m − 1 ( A i − B i + j ) 2 A i B i + j f_j=\sum_{i=0}^{m-1}(A_i-B_{i+j})^2A_iB_{i+j} fj=i=0m1(AiBi+j)2AiBi+j,然后令 ∗ * 的权值为 0 0 0

发现当 A , B A,B A,B 能匹配的情况只有当 f j = 0 f_j=0 fj=0 时。

仔细想一想, ( A i − B i ) 2 = 0 (A_i-B_i)^2=0 (AiBi)2=0 就是 A i = B i A_i=B_i Ai=Bi 的情况, A i = 0 A_i=0 Ai=0 就是 A A A 中有 ∗ * B i = 0 B_i=0 Bi=0 同理。

那么一样的套路,我们把 A A A 翻转一下,把下标扩展到 n n n,于是就有:

f j = ∑ i = 0 n − 1 ( A n − i + 1 − B i + j ) 2 A n − i + 1 B i + j f_j=\sum_{i=0}^{n-1}(A_{n-i+1}-B_{i+j})^2A_{n-i+1}B_{i+j} fj=i=0n1(Ani+1Bi+j)2Ani+1Bi+j

发现下标之和为 n − j + 1 n-j+1 nj+1,在 j j j 一定时为定值(即为卷积的形式)。

把它展开:

f j = ∑ i = 0 n − 1 A n − i + 1 3 B i + j − 2 ∑ i = 0 n − 1 A n − i + 1 2 B i + j 2 + ∑ i = 0 n − 1 A n − i + 1 B i + j 3 f_j=\sum_{i=0}^{n-1}A_{n-i+1}^3B_{i+j}-2\sum_{i=0}^{n-1}A^2_{n-i+1}B^2_{i+j}+\sum_{i=0}^{n-1}A_{n-i+1}B^3_{i+j} fj=i=0n1Ani+13Bi+j2i=0n1Ani+12Bi+j2+i=0n1Ani+1Bi+j3

于是用 f f t fft fft,判断一下 f j f_j fj 是否为 n n n 即可。


Code

#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 300005
#define ll long long
using namespace std;
const double pi=acos(-1);
int n,m,lim=1,tot;
int x[N],y[N],pos[N<<2],rec[N];
char A[N],B[N];
ll ans[N];
struct complex{
	double x,y;
	complex(){}
	complex(double x,double y):x(x),y(y){}
	friend complex operator+(const complex &a,const complex &b)  {return complex(a.x+b.x,a.y+b.y);}
	friend complex operator-(const complex &a,const complex &b)  {return complex(a.x-b.x,a.y-b.y);}
	friend complex operator*(const complex &a,const complex &b)  {return complex(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);}
}a[N<<2],b[N<<2];
void DFT(complex f[],int type){
	for(int i=0;i<lim;++i)
		if(pos[i]>i)  swap(f[i],f[pos[i]]);
	for(int mid=1;mid<lim;mid<<=1){
		complex now=complex(cos(pi/mid),type*sin(pi/mid));
		for(int i=0;i<lim;i+=(mid<<1)){
			complex w=complex(1,0);
			for(int j=0;j<mid;++j,w=w*now){
				complex p0=f[i+j],p1=w*f[i+j+mid];
				f[i+j]=p0+p1,f[i+j+mid]=p0-p1;
			}
		}
	}
	if(type==-1)  for(int i=0;i<lim;++i)  f[i].x/=lim;
}
void FFT(int k){
	DFT(a,1),DFT(b,1);
	for(int i=0;i<lim;++i)  a[i]=a[i]*b[i];
	DFT(a,-1);
	for(int i=0;i<n;++i)  ans[i]+=k*(ll)(a[i].x+0.5);
	for(int i=0;i<lim;++i)  a[i].x=a[i].y=b[i].x=b[i].y=0;
}
int main(){
	scanf("%d%d%s%s",&m,&n,A,B);
	while(lim<=(n<<1))  lim<<=1;
	for(int i=0;i<lim;++i)  pos[i]=(pos[i>>1]>>1)|((i&1)*(lim>>1));
	for(int i=0;i<m;++i)  if(A[i]!='*')  x[i]=A[i]-'a'+1;
	for(int i=0;i<n;++i)  if(B[i]!='*')  y[i]=B[i]-'a'+1;
	reverse(x,x+m);
	for(int i=0;i<n;++i)a[i].x=x[i]*x[i]*x[i],b[i].x=y[i];FFT(1);
	for(int i=0;i<n;++i)a[i].x=x[i]*x[i],b[i].x=y[i]*y[i];FFT(-2);
	for(int i=0;i<n;++i)a[i].x=x[i],b[i].x=y[i]*y[i]*y[i];FFT(1);
	for(int i=m-1;i<n;++i)  if(!ans[i])  rec[++tot]=i-m+2;
	printf("%d\n",tot);
	for(int i=1;i<=tot;++i)  printf("%d ",rec[i]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值