FFT专题

FFT

最近在学一个叫做FFT的东西(多项式我来啦

黑暗爆炸 - 4503
模板字符串匹配,卷一下就好啦

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=3e5+10; 
const double pi=acos(-1.0);

ll n,m;
char ch[maxn];
struct comp
{
    double x,y;
    comp(double xx=0,double yy=0){x=xx,y=yy;}
}a[maxn],b[maxn],a2[maxn],b2[maxn],b3;
comp operator + (comp a,comp b){ return comp(a.x+b.x,a.y+b.y);}
comp operator - (comp a,comp b){ return comp(a.x-b.x,a.y-b.y);}
comp operator * (comp a,comp b){ return comp(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);}

int l=0,r[maxn];
int limit=1;
void FFT(comp a[],int type){
    for(int i=0;i<limit;i++) 
        if(i<r[i])swap(a[i],a[r[i]]);  //求出要迭代的序列 
    for(int mid=1;mid<limit;mid<<=1)  //待合并区间的中点
    {
        comp wn(cos(pi/mid),type*sin(pi/mid));  //单位根 
        for(int R=mid<<1,j=0;j<limit;j+=R)  //R是区间的右端点,j表示前已经到哪个位置了 
        {
            comp w(1,0); //幂 
            for(int k=0;k<mid;k++,w=w*wn)  //枚举左半部分 
            {
                comp x=a[j+k],y=w*a[j+mid+k]; //蝴蝶变化 
                a[j+k]=x+y;
                a[j+mid+k]=x-y;
            }
        }
    }
}
vector<int>v;
int main()
{
	scanf("%s",ch);
	n=strlen(ch);
	for(int i=0;i<n;i++)
		if(ch[i]=='?')a[i].x=0;	 
		else a[i].x=ch[i]-'a'+1;
	scanf("%s",ch);
	m=strlen(ch);
	for(int i=0;i<m;i++)
		if(ch[i]=='?')b[i].x=0;	 
		else b[i].x=ch[i]-'a'+1;
	reverse(b,b+m);
	while(limit<=n+m)limit<<=1,l++;
	for(int i=0;i<limit;i++)
		a2[i]=a[i]*a[i],
		b2[i]=(comp){2,0}*b[i]*b[i],b3.x+=b[i].x*b[i].x*b[i].x;
    for(int i=0;i<limit;i++)
        r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
    FFT(a,1);FFT(b,1);FFT(a2,1);FFT(b2,1);
    for(int i=0;i<limit;i++){
    	a2[i]=a2[i]*b[i]-a[i]*b2[i];
	}
	FFT(a2,-1);
    for(int i=m-1;i<n;i++){
    	if((int)(a2[i].x/limit+b3.x+0.5)==0){
    		v.push_back(i-m+1);
		}
	}
	printf("%d\n",v.size());
	for(int i=0;i<v.size();i++){
		printf("%d\n",v[i]);
	}
	return 0;
}

黑暗爆炸 - 4259
同上题,模板

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=7e5+10; 
const double pi=acos(-1.0);

ll n,m;
char ch[maxn];
struct comp
{
    double x,y;
    comp(double xx=0,double yy=0){x=xx,y=yy;}
}a[maxn],b[maxn],a2[maxn],b2[maxn],b3[maxn],a3[maxn];
comp operator + (comp a,comp b){ return comp(a.x+b.x,a.y+b.y);}
comp operator - (comp a,comp b){ return comp(a.x-b.x,a.y-b.y);}
comp operator * (comp a,comp b){ return comp(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);}

int l=0,r[maxn];
int limit=1;
void FFT(comp a[],int type){
    for(int i=0;i<limit;i++) 
        if(i<r[i])swap(a[i],a[r[i]]);  //求出要迭代的序列 
    for(int mid=1;mid<limit;mid<<=1)  //待合并区间的中点
    {
        comp wn(cos(pi/mid),type*sin(pi/mid));  //单位根 
        for(int R=mid<<1,j=0;j<limit;j+=R)  //R是区间的右端点,j表示前已经到哪个位置了 
        {
            comp w(1,0); //幂 
            for(int k=0;k<mid;k++,w=w*wn)  //枚举左半部分 
            {
                comp x=a[j+k],y=w*a[j+mid+k]; //蝴蝶变化 
                a[j+k]=x+y;
                a[j+mid+k]=x-y;
            }
        }
    }
}
vector<int>v;
int main()
{
	scanf("%d%d",&n,&m);
	scanf("%s",ch);
	for(int i=0;i<n;i++)
		if(ch[i]=='*')a[i].x=0;	 
		else a[i].x=ch[i]-'a'+1;
	scanf("%s",ch);
	m=strlen(ch);
	for(int i=0;i<m;i++)
		if(ch[i]=='*')b[i].x=0;	 
		else b[i].x=ch[i]-'a'+1;
	reverse(a,a+n);
	while(limit<=n+m)limit<<=1,l++;
	for(int i=0;i<limit;i++)
		a2[i]=a[i]*a[i],
		b2[i]=(comp){2,0}*b[i]*b[i],
		a3[i]=a[i]*a[i]*a[i],
		b3[i]=b[i]*b[i]*b[i];
    for(int i=0;i<limit;i++)
        r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
    FFT(a3,1);FFT(b3,1);FFT(a2,1);FFT(b2,1);
    FFT(a,1);FFT(b,1);
    for(int i=0;i<limit;i++){
    	a2[i]=a3[i]*b[i]-a2[i]*b2[i]+b3[i]*a[i];
	}
	FFT(a2,-1);
    for(int i=n-1;i<m;i++){
    	if((int)(a2[i].x/limit+0.5)==0){
    		v.push_back(i-n+1);
		}
	}
	printf("%d\n",v.size());
	for(int i=0;i<v.size();i++){
		printf("%d ",v[i]+1);
	}
	return 0;
}

CodeForces - 528D
模糊字符串匹配,我太菜了,看了题解。
原来的想法,是预处理出A串中能匹配的字符,然后只要和B串卷积连乘就行了,比如式子是这样的:

但是这个式子展开也太离谱了!
题解的思路:
因为字符集只有’A,C,G,T’,我们把每个字符分开做贡献,如果看从一个位置开始能匹配几个给定字符,然后再把所有的字符累加即可,如果一项的系数是B串的长度,即意味着匹配成功。

对于一个字符c,
如果A串的第i个位置可以放c,那么 f i = 1 fi=1 fi=1,否则 f i = 0 fi=0 fi=0。 
如果B串的第i个位置可以放c,那么 g i = 1 gi=1 gi=1,否则 g i = 0 gi=0 gi=0
然后卷积:
h i = ∑ f j hi=∑fj hi=fj g i − j gi−j gij
然后把4个字符的解累加起来。
r e s i = ∑ h [ c ] resi=∑h[c] resi=h[c]
显然,如果 r e s [ i ] = ∣ B ∣ res [i]=|B| res[i]=B,那么从A串的第 i − ∣ B ∣ + 1 i−|B|+1 iB+1位开始可以匹配。
然后时间复杂度还是O( ( ∣ A ∣ + ∣ B ∣ ) l o g ( ∣ A ∣ + ∣ B ∣ ) (|A|+|B|)log(|A|+|B|) (A+B)log(A+B))。常数小了很多,也不用自己展开了。

代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=7e5+10; 
const double pi=acos(-1.0);

ll n,m,k;
struct comp
{
    double x,y;
    comp(double xx=0,double yy=0){x=xx,y=yy;}
}x[maxn],y[maxn],z[maxn];
comp operator + (comp a,comp b){ return comp(a.x+b.x,a.y+b.y);}
comp operator - (comp a,comp b){ return comp(a.x-b.x,a.y-b.y);}
comp operator * (comp a,comp b){ return comp(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);}

int l=0,r[maxn];
int limit=1;
void FFT(comp a[],int type){
    for(int i=0;i<limit;i++) 
        if(i<r[i])swap(a[i],a[r[i]]);  //求出要迭代的序列 
    for(int mid=1;mid<limit;mid<<=1)  //待合并区间的中点
    {
        comp wn(cos(pi/mid),type*sin(pi/mid));  //单位根 
        for(int R=mid<<1,j=0;j<limit;j+=R)  //R是区间的右端点,j表示前已经到哪个位置了 
        {
            comp w(1,0); //幂 
            for(int k=0;k<mid;k++,w=w*wn)  //枚举左半部分 
            {
                comp x=a[j+k],y=w*a[j+mid+k]; //蝴蝶变化 
                a[j+k]=x+y;
                a[j+mid+k]=x-y;
            }
        }
    }
}
int mp[300],a[maxn],b[maxn];
int res[maxn];
char ch[maxn];
int main()
{
	mp['A']=0;mp['C']=1;mp['G']=2;mp['T']=3;
	scanf("%d%d%d",&n,&m,&k);
	scanf("%s",ch);
	for(int i=0;i<n;i++){
		a[i]=mp[ch[i]];
	}
	scanf("%s",ch);
	for(int i=0;i<m;i++){
		b[i]=mp[ch[i]];
	}
	reverse(b,b+m);
	while(limit<=n+m)limit<<=1,l++;
	for(int i=0;i<limit;i++)
        r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
    memset(res,0,sizeof res);
    for(int i=0;i<4;i++){
    	for(int j=0;j<limit;j++){
    		x[j].x=y[j].x=0;
    		x[j].y=y[j].y=0;
		}
		int cnt=0;
		for(int j=0;j<k;j++)
			if(a[j]==i)cnt++;
		for(int j=0;j<n;j++){
			if(j+k<n)
				if(a[j+k]==i)cnt++;
			if(j-k-1>=0)
				if(a[j-k-1]==i)cnt--;
			if(cnt>0)x[j].x=1;
		}
		for(int j=0;j<m;j++){
			if(b[j]==i)y[j].x=1;
		}
		FFT(x,1);FFT(y,1);
		for(int j=0;j<limit;j++){
			z[j]=x[j]*y[j];
		}
		FFT(z,-1);
		for(int j=0;j<n;j++){
			res[j]+=(int(z[j].x/limit+0.5));
		}
	}
	int ans=0;
	for(int i=m-1;i<n;i++)
		if(res[i]==m)ans++;
	printf("%d\n",ans);
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值