BZOJ 3527 [ZJOI 3014] 力 (FFT)

题目链接:BZOJ 3527

因为这道题在BZOJ上没有贴,所以我还是附上数据吧。

input:

5
4006373.885184
15375036.435759
1717456.469144
8514941.004912
1410681.345880


output:
-16838672.693
3439.793
7509018.566
4595686.886
10903040.872


做完这道题的第一感受是:我也是会写FFT的人了!!!(虽说只会写模板= =)。这道题还算是比较裸的FFT,我也是来学习写FFT的。题解什么的,由于涉及到许多高数知识,博主蒟蒻知道的少,所以我还是简单说说吧。。。

简单说一下FFT吧。之前一直不知道FFT怎么求卷积,现在才知道。对于函数y[ ],f[ ],g[ ],满足y[n]=f[n]*g[n],快速求得y[n],设函数F[f]=DET(f[n]),G[f]=DET(g[n]),那么函数y[n]可以表示为:y[n]=IDFT(DFT(f[n])*DFT(g[n]))。而对于卷积(f*g)[m]=segma(f[n]*g[m-n])就可以快速求出,简化乘法和加法运算。因为FFT是基于复数运算的,对于一次复数的乘法运算相当于四次实数乘法运算和二次实数加法,对于一次复数加法相当于二次实数加法,这样就简化了运算。

代码中有一个小技巧,即观察计算DFT和IDFT的式子,我们发现两者只相差一个符号和一个常数。代码中的v参数就是来改变符号的,至于对于要除掉的那个常数,可以在最后输出答案的时候除掉,这样就只用写一个函数。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
using namespace std;
int N,M;
double pi=acos(-1);

struct complex{
	double r,i;
	complex(double r=0,double i=0):r(r),i(i){}
	complex operator + (const complex &A)const{
		return complex(r+A.r,i+A.i);
	}
	complex operator - (const complex &A)const{
		return complex(r-A.r,i-A.i);
	}
	complex operator * (const complex &A)const{
		return complex(r*A.r-i*A.i,r*A.i+i*A.r);
	}
};
complex A[600000+10],B[600000+10];

void DFT(complex A[],int v=0){//v用于辨别当前是进行DFT还是IDFT操作
	for(int i=0,j=0;i<M;i++){
		if(i<j)swap(A[i],A[j]);
		for(int k=M>>1;(j^=k)<k;k>>=1)
			;
	}
	for(int i=1;i<M;i<<=1){
		complex w(1.,0.),wn(cos(pi/i),sin(pi/i));
		if(v)wn.i=-wn.i;
		for(int j=0;j<i;j++){
			for(int k=j;k<M;k+=i*2){
				complex u=A[k],v=A[k+i]*w;
				A[k]=u+v,A[k+i]=u-v;
			}
			w=w*wn;
		}
	}
}

void FFT(){
	for(M=1;M<2*N-1;M<<=1)
		;
	M<<=1;
	for(int i=N;i<M;i++)A[i]=0;
	for(int i=2*N-1;i<M;i++)B[i]=0;
	DFT(A);
	DFT(B);
	for(int i=0;i<M;i++)A[i]=A[i]*B[i];
	DFT(A,1);
	for(int i=0;i<N;i++){
		double t=A[i+N-1].r/M;//因为最后一步是IDFT操作所以要除以一个常数
		printf("%lf\n",t);
	}
}

int main(){
	scanf("%d",&N);
	for(int i=0;i<N;i++)scanf("%lf",&A[i]);
	for(int i=N-2;i>=0;i--){
		double t=i+1;
		B[N-i-2]=-1.0/t/t;
	}
	B[N-1]=0;
	for(int i=1;i<N;i++){
		double t=i;
		B[i+N-1]=1.0/t/t;
	}
	FFT();
	return 0;
}
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值