摸鱼之 - FFT的例题

【学习笔记】摸鱼之 - F F T \mathrm{FFT} 的例题

  • 我刚学 F F T \mathrm{FFT} 不到 1 1 天,做了几道题目来回顾一下(大佬勿喷
  • 如果你不会 F F T \mathrm{FFT} 一个不错的博客

例题 1 1 ZJOI2014 力

S o l \mathrm{Sol}

  • F j = i = 1 j 1 q i × q j ( i j ) 2 i = j + 1 n q i × q j ( i j ) 2 F_j=\sum_{i=1}^{j-1} \frac{q_i\times q_j}{(i-j)^2} - \sum_{i=j+1}^{n} \frac{q_i\times q_j}{(i-j)^2}
  • 那么 E j = F j q j = i = 1 j 1 q j ( i j ) 2 i = j + 1 n q j ( i j ) 2 E_j=\frac{F_j}{q_j}=\sum_{i=1}^{j-1} \frac{q_j}{(i-j)^2} - \sum_{i=j+1}^{n} \frac{q_j}{(i-j)^2}
  • 我们设 f i = q i , g i = 1 i 2 f_i=q_i,g_i=\frac{1}{i^2}
  • 那么 E j = i = 0 j 1 f i × g j i i = j + 1 n f i × g j i E_j=\sum_{i=0}^{j-1}f_i\times g_{j-i}-\sum_{i=j+1}^{n}f_i\times g_{j-i}
  • 我们再设 h i h_i f i f_i 反向后的值
  • 那么 E j = i = 0 j 1 f i × g j i i = 0 j 1 h i × g j i E_j=\sum_{i=0}^{j-1}f_i\times g_{j-i}-\sum_{i=0}^{j-1}h_i\times g_{j-i}
  • 这样子就成了卷积形式用 F F T \mathrm{FFT} 即可。

C o d e \mathrm{Code}

#include <iostream>
#include <cstdio>
#include <cmath>
#define pb push_back
using namespace std;

inline int read()
{
	int sum=0,ff=1; char ch=getchar();
	while(!isdigit(ch))
	{
		if(ch=='-') ff=-1;
		ch=getchar();
	}
	while(isdigit(ch))
		sum=sum*10+(ch^48),ch=getchar();
	return sum*ff;
}

const int N=3e5+5;
const double PI=acos(-1.0);

struct complex 
{
	double x,y;
	complex (double xx=0,double yy=0) { x=xx; y=yy; }
};
complex a[N],b[N],c[N];

int lim=1,l,r[N],n;

inline complex operator + (complex a,complex b)
{
	return complex(a.x+b.x,a.y+b.y);
}

inline complex operator - (complex a,complex b)
{
	return complex(a.x-b.x,a.y-b.y);
}

inline complex operator * (complex a,complex b)
{
	return complex(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);
}

inline void fft(complex *A,int inv)
{
	for ( int i=0;i<lim;i++ ) 
		if(i<r[i]) swap(A[i],A[r[i]]);
	for ( int mid=1;mid<lim;mid<<=1 )
	{
		complex dwg(cos(PI/mid),inv*sin(PI/mid));
		for ( int R=mid<<1,j=0;j<lim;j+=R )
		{
			complex w(1,0);
			for ( int k=0;k<mid;k++,w=w*dwg ) 
			{
				complex x=A[j+k],y=w*A[j+mid+k];
				A[j+k]=x+y;
				A[j+mid+k]=x-y;
			}
		}
	}
}

int main()
{
	scanf("%d",&n);
	for ( int i=1;i<=n;i++ ) 
	{
		double x;
		scanf("%lf",&a[i].x);
		b[n-i+1].x=a[i].x;
	}
	while(lim<=n*2) lim<<=1,l++;
	for ( int i=1;i<=n;i++ ) c[i].x=(double)(1.0/(double)i/(double)i);
	for ( int i=0;i<lim;i++ ) r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
	fft(a,1); 
	fft(b,1);
	fft(c,1);
	for ( int i=0;i<lim;i++ ) 
	{
		a[i]=a[i]*c[i];
		b[i]=b[i]*c[i];
	}
	fft(a,-1);
	fft(b,-1);
	for ( int i=0;i<lim;i++ ) 
	{
		a[i].x/=lim;
		b[i].x/=lim;
	}
	for ( int i=1;i<=n;i++ ) 
		printf("%.5lf\n",a[i].x-b[n-i+1].x);
	return 0;
}

例题 2 2 TJOI2017 DNA

S o l \mathrm{Sol}

  • 这道题目可以用 S A \mathrm{SA} 解决,当然 F F T \mathrm{FFT} 也是一个很好的选择
  • 我们首先要想到一个判断只改变 3 3 个以内字符完成匹配的方法。因为只有四个字符 A , T , C , G \mathrm{A,T,C,G} 。我们对于每一个字符做一下处理:比如对于 A \mathrm{A} 来说,我们把 S 0 S_0 中除了 A \mathrm{A} 以外的字符标记成 0 0 ,把 A \mathrm{A} 标记为 1 1 。那么对于每一个其实位置 k k S k . . k + m 1 S_{k..k+m-1} T 0.. m 1 T_{0..m-1} 的匹配数即为 i = 0 m 1 S k + i × T i \sum_{i=0}^{m-1} S_{k+i}\times T_i
  • 于是我们设 A m i 1 = T i A_{m-i-1}=T_i
  • 那么原式变为 i = 0 m 1 S k + i × A m i 1 \sum_{i=0}^{m-1}S_{k+i}\times A_{m-i-1}
  • 又变为 i + j = m k 1 S i × A j \sum_{i+j=m-k-1}S_i\times A_j
  • 这样子就成了卷积形式用 F F T \mathrm{FFT} 即可。

C o d e \mathrm{Code}

#include <iostream>
#include <cstdio>
#include <cmath>
#define pb push_back
using namespace std;

inline int read()
{
	int sum=0,ff=1; char ch=getchar();
	while(!isdigit(ch))
	{
		if(ch=='-') ff=-1;
		ch=getchar();
	}
	while(isdigit(ch))
		sum=sum*10+(ch^48),ch=getchar();
	return sum*ff;
}

const int N=5e5+5;
const double PI=acos(-1.0);

struct complex 
{
	double x,y;
	complex (double xx=0,double yy=0) { x=xx; y=yy; }
};
complex a[N],b[N],c[N];

int lim=1,l,r[N],n,m,ans[N];
string S,T;

inline complex operator + (complex a,complex b)
{
	return complex(a.x+b.x,a.y+b.y);
}

inline complex operator - (complex a,complex b)
{
	return complex(a.x-b.x,a.y-b.y);
}

inline complex operator * (complex a,complex b)
{
	return complex(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);
}

inline void fft(complex *A,int inv)
{
	for ( int i=0;i<lim;i++ ) 
		if(i<r[i]) swap(A[i],A[r[i]]);
	for ( int mid=1;mid<lim;mid<<=1 )
	{
		complex dwg(cos(PI/mid),inv*sin(PI/mid));
		for ( int R=mid<<1,j=0;j<lim;j+=R )
		{
			complex w(1,0);
			for ( int k=0;k<mid;k++,w=w*dwg ) 
			{
				complex x=A[j+k],y=w*A[j+mid+k];
				A[j+k]=x+y;
				A[j+mid+k]=x-y;
			}
		}
	}
}

int main()
{
	int TT=read();
	for (;TT--;)
	{
		cin>>S>>T;
		n=S.length();
		m=T.length();
		for ( int kind=1;kind<=4;kind++ ) 
		{
			char alb;
			if(kind==1) alb='A';
			if(kind==2) alb='T';
			if(kind==3) alb='C';
			if(kind==4) alb='G';
			for ( int i=0;i<n;i++ ) a[i].x=(alb==S[i]);
			for ( int i=0;i<m;i++ ) b[m-i-1].x=(alb==T[i]);
			lim=1,l=0;
			while(lim<=n+m) lim<<=1,l++;
			for ( int i=0;i<lim;i++ ) r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));	
			fft(a,1);
			fft(b,1);
			for ( int i=0;i<lim;i++ ) c[i]=a[i]*b[i];
			fft(c,-1);
			for ( int i=m-1;i<=n+m-1;i++ ) 
				ans[i-m+1]+=(int)(c[i].x/lim+0.5);
			for ( int i=0;i<lim;i++ ) 
				a[i]=b[i]=(complex){0,0};
		}
		int res=0;
		for ( int i=0;i<n-m+1;i++ )
			if(ans[i]+3>=m)
				res++;
		printf("%d\n",res);
		for ( int i=0;i<=max(n,m);i++ ) ans[i]=0;
	}
	return 0;
}		

例题 3 3 残缺的字符串

S o l \mathrm{Sol}

  • 首先我们考虑没有 * 的匹配即: F ( S , T ) = i = 0 n 1 ( S i T i ) 2 F(S,T)=\sum_{i=0}^{n-1}(S_i-T_i)^2 。有人可能会问,为什么不写成 F ( S , T ) = i = 0 n 1 S i T i F(S,T)=\sum_{i=0}^{n-1}|S_i-T_i| ,虽然在意思上是相同的,但是绝对值写法不能加速了,而上一种可以转化 F ( S , T ) = i = 0 n 1 ( S i + T i ) 2 2 i = 0 n 1 S i × T i F(S,T)=\sum_{i=0}^{n-1}(S_i+T_i)^2-2*\sum_{i=0}^{n-1}S_i\times T_i ,而这后半部分又可以转化为卷积形式即 i = 0 n 1 S i × T n i 1 \sum_{i=0}^{n-1}S_i\times T^{'}_{n-i-1} ,这可以快速 F F T \mathrm{FFT} 计算。
  • 然后我们考虑有 * 的匹配:因为 * 什么都可以匹配所以把他看作 0 0 (因为 F ( S , T ) = i = 0 n 1 ( S i T i ) 2 = 0 F(S,T)=\sum_{i=0}^{n-1}(S_i-T_i)^2=0 才算匹配成功)。那么我们考虑有哪几种情况 S i = T i S_i=T_i
    • S i = T i S_i=T_i
    • S i = S_i=*
    • T i = T_i=*
  • 所以我们构造新的 F ( S , T ) F(S,T) ,即 F ( S , T ) = i = 0 n 1 ( S i T i ) 2 × S i × T i F(S,T)=\sum_{i=0}^{n-1}(S_i-T_i)^2\times S_i\times T_i 。这样子只要三种情况满足其一即可匹配(这还是比较好理解的)。但上式好象用处不大,我们尝试把他拆开:
  • 这样就变为 F ( S , T ) = i = 0 n 1 S i 3 T i 2 i = 0 n 1 S i 2 T i 2 i = 0 n 1 S i T i 3 F(S,T)=\sum_{i=0}^{n-1}S_i^3T_i-2\sum_{i=0}^{n-1}S_i^2T_i^2-\sum_{i=0}^{n-1}S_iT_i^3
  • 然后将其翻转: F ( S , T ) = i = 0 n 1 S i 3 T n i 1 2 i = 0 n 1 S i 2 T n i 1 2 i = 0 n 1 S i T n i 1 3 F(S,T)=\sum_{i=0}^{n-1}S_i^3T^{'}_{n-i-1}-2\sum_{i=0}^{n-1}S_i^2T_{n-i-1}^{'2}\sum_{i=0}^{n-1}S_iT_{n-i-1}^{'3}
  • 那么就简单了,分别对各项做 F F T \mathrm{FFT} 即可,时间复杂度 O ( 6 × n l o g n ) O(6\times nlog n)

C o d e \mathrm{Code}

#include <iostream>
#include <cstdio>
#include <cmath>
#include <vector>
using namespace std;

inline int read()
{
	int sum=0,ff=1; char ch=getchar();
	while(!isdigit(ch))
	{
		if(ch=='-') ff=-1;
		ch=getchar();
	}
	while(isdigit(ch))
		sum=sum*10+(ch^48),ch=getchar();
	return sum*ff;
}

const int N=1e6+2e5+5;
const double PI=acos(-1.0);

struct complex 
{
	double x,y;
	complex (double xx=0,double yy=0) { x=xx,y=yy; }
};
complex a[N],b[N],c[N],d[N],e[N],f[N];

int l,lim=1,r[N],s[N],t[N],n,m;
string S,T;
vector<int> ans;

inline complex operator + (complex a,complex b)
{
	return complex(a.x+b.x,a.y+b.y);
}

inline complex operator - (complex a,complex b)
{
	return complex(a.x-b.x,a.y-b.y);
}

inline complex operator * (complex a,complex b)
{
	return complex(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);
}

inline void fft(complex *A,int inv)
{
	for ( int i=0;i<lim;i++ ) 
		if(i<r[i]) swap(A[i],A[r[i]]);
	for ( int mid=1;mid<lim;mid<<=1 )
	{
		complex dwg(cos(PI/mid),inv*sin(PI/mid));
		for ( int R=mid<<1,j=0;j<lim;j+=R )
		{
			complex w(1,0);
			for ( int k=0;k<mid;k++,w=w*dwg ) 
			{
				complex x=A[j+k],y=w*A[j+mid+k];
				A[j+k]=x+y;
				A[j+mid+k]=x-y;
			}
		}
	}
}

int main()
{
	m=read();
	n=read();
	cin>>T;
	cin>>S;
	for ( int i=0;i<n;i++ ) s[i]=(S[i]=='*')?0:(S[i]-'a'+1);
	for ( int i=0;i<m;i++ ) t[m-i-1]=(T[i]=='*')?0:(T[i]-'a'+1);
	while(lim<=n+m) lim<<=1,l++;
	for ( int i=0;i<lim;i++ ) r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));	
	for ( int i=0;i<n;i++ ) 
	{
		a[i].x=s[i]*s[i]*s[i];
		b[i].x=-2*s[i]*s[i];
		c[i].x=s[i];
	}
	for ( int i=0;i<m;i++ )
	{
		d[i].x=t[i];
		e[i].x=t[i]*t[i];
		f[i].x=t[i]*t[i]*t[i];
	}
	fft(a,1); fft(b,1); fft(c,1);
	fft(d,1); fft(e,1); fft(f,1);
	for ( int i=0;i<lim;i++ )
		a[i]=a[i]*d[i]+b[i]*e[i]+c[i]*f[i];
	fft(a,-1);
	for ( int i=0;i<n-m+1;i++ ) 
		if((int)(a[i+m-1].x/lim+0.5)==0) ans.push_back(i+1);
	printf("%d\n",ans.size());
	for ( int i=0;i<ans.size();i++ ) 
		printf("%d ",ans[i]);
	return 0;
} 
			
	
	 

猜你喜欢

转载自blog.csdn.net/wangyiyang2/article/details/105031048
FFT
今日推荐