bzoj 4503: 两个串 fft

       没想到这种字符串题居然可以用fft。。。

       设有两个串S1,S2,定义S1和S2的距离为:Σ(i=1,n) (s1[i]-s2[i])^2,那么两个串相同当且仅当距离=0。那么现在S2存在通配字符'?',那么;令'?'的值为0,就可以修改一下距离为:Σ(i=1,n) s2[i]*(s1[i]-s2[i])^2。

       那么把S2翻转一下就变成卷积的形式了,直接上fft即可。注意可以把多次的点值的结果并在一起最后使用一次插值即可。

AC代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#define pi acos(-1.0)
#define N 300005
using namespace std;

struct cpx{ double r,i; }a[N],b[N],c[N]; int n1,n2,m,f[N],g[N],pos[N];
char s1[N],s2[N];
cpx operator +(cpx x,cpx y){ x.r+=y.r; x.i+=y.i; return x; }
cpx operator -(cpx x,cpx y){ x.r-=y.r; x.i-=y.i; return x; }
cpx operator *(cpx x,cpx y){
	cpx t; t.r=x.r*y.r-x.i*y.i; t.i=x.r*y.i+x.i*y.r; return t;
}
void dft(cpx *a,int flag){
	int i,j,k; cpx w,wn,u,v;
	if (flag>0) for (i=0; i<m; i++) a[i].i=0;
	for (k=1; k<m; k<<=1){
		wn.r=cos(pi*flag/k); wn.i=sin(pi*flag/k);
		for (i=0; i<m; i+=(k<<1)){
			w.r=1; w.i=0;
			for (j=i; j<i+k; j++){
				u=a[j]; v=a[j+k]*w;
				a[j]=u+v; a[j+k]=u-v; w=w*wn;
			}
		}
	}
	if (flag<0) for (i=0; i<m; i++) a[i].r/=m;
}
int main(){
	scanf("%s%s",s1+1,s2+1); int i,j,k,cnt=0;
	n1=strlen(s1+1); n2=strlen(s2+1);
	for (i=1; i<=n1; i++) f[i]=s1[i]-'a'+1;
	for (i=1; i<=n2; i++) g[n2-i+1]=(s2[i]=='?')?0:s2[i]-'a'+1;
	m=n1+n2+1;
	for (i=1; i<m; i<<=1) cnt++; m=i;
	for (i=0; i<m; i++)
		for (k=i,j=1; j<=cnt; j++,k>>=1) pos[i]=pos[i]<<1|(k&1);
	for (i=0; i<m; i++){ a[pos[i]].r=f[i]*f[i]; b[pos[i]].r=g[i]; }
	dft(a,1); dft(b,1);
	for (i=0; i<m; i++) c[pos[i]]=a[i]*b[i];
	for (i=0; i<m; i++){ a[pos[i]].r=1;  b[pos[i]].r=g[i]*g[i]*g[i]; }
	dft(a,1); dft(b,1);
	for (i=0; i<m; i++) c[pos[i]]=c[pos[i]]+a[i]*b[i];
	for (i=0; i<m; i++){ a[pos[i]].r=f[i]*2; b[pos[i]].r=g[i]*g[i]; }
	dft(a,1); dft(b,1);
	for (i=0; i<m; i++) c[pos[i]]=c[pos[i]]-a[i]*b[i];
	dft(c,-1); cnt=0;
	for (i=1; i<=n1-n2+1; i++) if (c[i+n2].r<0.5) f[++cnt]=i;
	printf("%d\n",cnt);
	for (i=1; i<=cnt; i++) printf("%d\n",f[i]-1);
	return 0;
}


by lych

2016.4.11

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值