bzoj 4503 两个串

本文介绍了一种使用快速傅立叶变换(FFT)的算法来解决含有通配符 '?' 的字符串匹配问题,通过将模式串翻转并利用FFT进行高效的卷积运算,实现对目标串中所有匹配模式的定位。

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

4503: 两个串

Time Limit: 20 Sec   Memory Limit: 256 MB
Submit: 580   Solved: 259
[ Submit][ Status][ Discuss]

Description

兔子们在玩两个串的游戏。给定两个字符串S和T,兔子们想知道T在S中出现了几次,
分别在哪些位置出现。注意T中可能有“?”字符,这个字符可以匹配任何字符。

Input

两行两个字符串,分别代表S和T

Output

第一行一个正整数k,表示T在S中出现了几次
接下来k行正整数,分别代表T每次在S中出现的开始位置。按照从小到大的顺序输出,S下标从0开始。

Sample Input

bbabaababaaaaabaaaaaaaabaaabbbabaaabbabaabbbbabbbbbbabbaabbbababababbbbbbaaabaaabbbbbaabbbaabbbbabab
a?aba?abba

Sample Output

0

HINT

S 长度不超过 10^5, T 长度不会超过 S。 S 中只包含小写字母, T中只包含小写字母和“?”





【分析】

hzwer版本的#include<complex>太慢了...换了licone的struct complex 版本,跑起来挺快

FFT这个东西就是要跑脑洞...

这个题应该算是通配符匹配问题中的一类吧

把模式串翻转一下

令 c[j+m-1]=sigama{(a[j+i]-b[m-1-i)^2*b[m-1-i]}

展开以后有三只卷鸡,然后黑箱FFT = =

(还是要多加练习FFT啊...每次根本看不出来qwq)

(其实我现在才知道卷鸡可以带平方三次方什么的...黑箱果然还是不太好)



【代码】

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#define pi acos(-1)
#define ll long long
#define M(a) memset(a,0,sizeof a)
#define fo(i,j,k) for(int i=j;i<=k;i++)
using namespace std;
const int mxn=400005;
char s[mxn];
int n,m,L,N,M;
int R[mxn],ans[mxn];
double pre[mxn],now[mxn];
struct E
{
	double r,f;  //real & fake
	E (double u,double v) {r=u,f=v;}
	E () {}
	E operator + (E u) {return E(r+u.r,f+u.f);}
	E operator - (E u) {return E(r-u.r,f-u.f);}
	E operator * (E u) {return E(r*u.r-f*u.f,r*u.f+f*u.r);}
	E operator / (int v) {return E(r/v,f/v);}
}a[mxn],b[mxn],c[mxn];
inline void fft(E *a,int f)
{
	fo(i,0,n-1) if(i<R[i]) swap(a[i],a[R[i]]);
	for(int i=1;i<n;i<<=1)
	{
		E wn(cos(pi/i),f*sin(pi/i));
		for(int j=0;j<n;j+=(i<<1))
		{
			E w(1,0);
			for(int k=0;k<i;k++,w=w*wn)
			{
				E x=a[j+k],y=w*a[j+k+i];
				a[j+k]=x+y,a[j+k+i]=x-y;
			}
		}
	}
	if(f==-1) fo(i,0,n-1) a[i]=a[i]/n;
}
int main()
{
    scanf("%s",s);n=strlen(s);
    fo(i,0,n-1) pre[i]=(double)s[i]-'a'+1;
    scanf("%s",s);m=strlen(s);
    fo(i,0,m-1) now[i]=s[m-i-1]=='?'?(double)0:(double)s[m-i-1]-'a'+1;
    M=2*n;for(n=1;n<M;n<<=1) L++;M/=2;
    fo(i,0,n-1) R[i]=(R[i>>1]>>1|((i&1)<<L-1));
    //a^2*b
    fo(i,0,M-1) a[i].r=pre[i]*pre[i],b[i].r=now[i];
    fft(a,1),fft(b,1);
    fo(i,0,n-1) c[i]=a[i]*b[i];
    fo(i,0,n-1) a[i].r=a[i].f=b[i].r=b[i].f=0;
    //2*a*b^2
    fo(i,0,M-1) a[i].r=pre[i],b[i].r=now[i]*now[i];
    fft(a,1),fft(b,1);
    fo(i,0,n-1) c[i]=c[i]-a[i]*b[i]-a[i]*b[i];
    fo(i,0,n-1) a[i].r=a[i].f=b[i].r=b[i].f=0;
    //b^3
    fo(i,0,M-1) a[i].r=(double)1,b[i].r=now[i]*now[i]*now[i];
	fft(a,1),fft(b,1);
	fo(i,0,n-1) c[i]=c[i]+a[i]*b[i];
	fft(c,-1);
	fo(i,m-1,M-1) if(fabs(c[i].r)<0.5) ans[++ans[0]]=i-m+1;
	fo(i,0,ans[0]) printf("%d\n",ans[i]);
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值