zoukankan      html  css  js  c++  java
  • [算法模板]FFT-快速傅里叶变换

    [算法模板]FFT-快速傅里叶变换

    感谢ZYW聚聚为我们讲解FFT~

    FFT

    思路

    我懒,思路和证明部分直接贴链接:

    rvalue

    LSJ-FFT与NTT基础

    代码

    主要思想是利用了单位根特殊的性质(n次单位根后一半幂跟前一半幂取值相等)。只是因为式子中奇数次幂还要提出来个(omega_n^k),这个东西只要取个反就好了(即对称性:(omega_n^k=-omega_n^{k+frac{n}{2}}))。

    FFT递归:

    #include <cstdio>
    #include <cmath>
    using namespace std;
    const int maxn=2e6+10;
    const double pi=acos(-1.0);
    struct comp{
    	double a,b;
    };
    comp operator +(comp a,comp b){return (comp){a.a+b.a,a.b+b.b};}
    comp operator -(comp a,comp b){return (comp){a.a-b.a,a.b-b.b};}
    comp operator *(comp a,comp b){return (comp){a.a*b.a-a.b*b.b,a.a*b.b+a.b*b.a};}
    void fft(int l,comp *a,int f)
    {
    	if(l==1) return;
    	comp a1[l>>1],a2[l>>1];
    	for(int i=0;i<l;i+=2)
    	{
    		a1[i>>1]=a[i];
    		a2[i>>1]=a[i+1];
    	}
    	fft(l>>1,a1,f); fft(l>>1,a2,f);
    	comp wn=(comp){cos(2*pi/l),f*sin(2*pi/l)},w=(comp){1,0};
    	for(int i=0;i<(l>>1);i++,w=w*wn)
    	{
    		a[i]=a1[i]+w*a2[i];
    		a[i+(l>>1)]=a1[i]-w*a2[i];
    	}
    }
    comp a[maxn],b[maxn];
    int main ()
    {
    	int n,m; scanf("%d%d",&n,&m);
    	for(int i=0;i<=n;i++) scanf("%lf",&a[i].a);
    	for(int i=0;i<=m;i++) scanf("%lf",&b[i].a);
    	int l=1; while(l<=n+m) l<<=1;
    	fft(l,a,1); fft(l,b,1);
    	for(int i=0;i<l;i++) a[i]=a[i]*b[i];
    	fft(l,a,-1);
    	for(int i=0;i<=n+m;i++) printf("%d ",(int)(a[i].a/l+0.5));
    	return 0;
    }
    

    因为其运行效率过低。我们一般使用迭代FFT。

    FFT迭代:

    #include <cstdio>
    #include <cmath>
    #include <iostream> 
    using namespace std;
    const int maxn=4*1e6+10;
    const double pi=acos(-1.0);
    struct comp{
    	double a,b;
    };
    comp operator +(comp a,comp b){return (comp){a.a+b.a,a.b+b.b};}
    comp operator -(comp a,comp b){return (comp){a.a-b.a,a.b-b.b};}
    comp operator *(comp a,comp b){return (comp){a.a*b.a-a.b*b.b,a.a*b.b+a.b*b.a};}
    int rev[maxn],rp;
    void get_rev(int l)//l为位数,rev[i]代表i的二进制表示颠倒(二进制位有l位,不足补0) 
    {
    	for(int i=1;i<(1<<l);i++)
    	rev[i]=(rev[i>>1]>>1)|((1&i)<<l-1);
    }
    void fft(int len,comp *a,int f)
    {
    	for(int i=1;i<len;i++)
    	if(rev[i]>i) swap(a[rev[i]],a[i]);
    	for(int l=2;l<=len;l<<=1)//区间长度 
    	{
    		comp wn=(comp){cos(2*pi/l),f*sin(2*pi/l)};	
    		for(int i=0;i+l<=len;i+=l)
    		{
    			comp w=(comp){1,0};
    			for(int k=i;k<i+(l>>1);k++,w=w*wn)
    			{
    				comp t=w*a[k+(l>>1)],tmp=a[k];
    				a[k]=tmp+t;
    				a[k+(l>>1)]=tmp-t;
    			}
    		}
    	}			 
    }
    //a[i]表示当x=单位根的i次方时y的值 
    comp a[maxn],b[maxn];
    int main ()
    {
    	int n,m; scanf("%d%d",&n,&m);
    	for(int i=0;i<=n;i++) scanf("%lf",&a[i].a);
    	for(int i=0;i<=m;i++) scanf("%lf",&b[i].a);
    	int l=1,cnt=0; while(l<=n+m) l<<=1,cnt++;
    	get_rev(cnt);
    	fft(l,a,1); fft(l,b,1);//l是多项式项数
    	for(int i=0;i<l;i++) a[i]=a[i]*b[i];
    	fft(l,a,-1);
    	for(int i=0;i<=n+m;i++) printf("%d ",(int)(a[i].a/l+0.5));
    	return 0;
    }
    

    NTT

    啊我饿了我要吃NTT

    直接粘一张钟神的PPT:

    代码

    预处理原根次幂:

    for(int i=2;i<(1<<l);i<<=1) {//枚举单位根周期长度(即w_n的n)
    		int w0=Pow(3,(P-1)/i),w1=Pow(3,P-1-(P-1)/i);
    		wn[0][i>>1]=wn[1][i>>1]=1;//wn[f][i],i的最高位代表是几次单位根,其他位代表是第几个。这里求的是i的单位根,因为前一半i单位根等于i/2的单位根所以是存储在i/2的位置.(推式子的时候推过,长度为len时代入单位根周期为len/2)
    		for(int j=1;j<(i>>1);++j)//w_i单位根的j次方(因为折半了所以只用求一半)
    			wn[0][(i>>1)+j]=wn[0][(i>>1)+j-1]*(ll)w0%P,
    			wn[1][(i>>1)+j]=wn[1][(i>>1)+j-1]*(ll)w1%P;
    }
    

    [模板]分治FFT

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    using namespace std;
    const int mod=998244353;
    const int maxn=3e5+10;
    typedef long long ll;
    ll a[maxn],b[maxn],f[maxn],g[maxn],wn[2][maxn];
    int n,rev[maxn];
    int ksm(int num,int t){
    	int res=1;
    	for(;t;t>>=1,num=1ll*num*num%mod){
    		if(t&1)res=1ll*res*num%mod;
    	}
    	return res;
    }
    void get_rev(int len){for(int i=1;i<(1<<len);i++)rev[i]=(rev[i>>1]>>1)|((i&1)<<(len-1));}
    void get_wn(int len){
    	for(int i=2;i<=(1<<len);i<<=1){
    		ll w1=ksm(3,(mod-1)/i),w0=ksm(3,mod-1-(mod-1)/i);
    		wn[0][i>>1]=wn[1][i>>1]=1;
    		for(int j=1;j<(i>>1);j++){
    			wn[0][j+(i>>1)]=wn[0][j+(i>>1)-1]*w0%mod;	
    			wn[1][j+(i>>1)]=wn[1][j+(i>>1)-1]*w1%mod;	
    		}
    	}
    }
    void NTT(int len,ll *c,int f){
    	for(int i=0;i<len;i++)if(rev[i]>i)swap(c[i],c[rev[i]]);
    	for(int l=2;l<=len;l<<=1){
    		for(int i=0;i+l<=len;i+=l){
    			for(int k=i;k<i+(l>>1);k++){
    				ll tmp1=c[k],tmp2=wn[f][k+(l>>1)-i]*c[k+(l>>1)];
    				c[k]=(tmp1+tmp2)%mod;
    				c[k+(l>>1)]=(tmp1-tmp2+mod)%mod;	
    			}
    		}
    	}
    }
    void cdq(int l,int r){
    	if(l==r)return;
    	int mid=(l+r)>>1;
    	cdq(l,mid);
    	int cnt=0,len=1;while(len<=(r-l-1))len<<=1,cnt++;
    	for(int i=0;i<len;i++)a[i]=b[i]=0;
    	for(int i=0;i<=mid-l;i++)a[i]=f[i+l];	
    	for(int i=0;i<=r-l-1;i++)b[i]=g[i+1];
    //	memset(rev,0,sizeof(rev));
    	get_rev(cnt);
    	NTT(len,a,1);NTT(len,b,1);
    	for(int i=0;i<len;i++)a[i]=a[i]*b[i]%mod;
    	NTT(len,a,0);
    	ll inv=ksm(len,mod-2);
    	for(int i=0;i<len;i++)a[i]=a[i]*inv%mod;
    	for(int i=mid+1;i<=r;i++)f[i]+=a[i-l-1],f[i]%=mod;
    	cdq(mid+1,r);
    }
    int main(){
    	f[0]=1;
    	scanf("%d",&n);get_wn(18);
    	for(int i=1;i<n;i++)scanf("%lld",&g[i]);
    	cdq(0,n-1);
    	for(int i=0;i<n;i++)printf("%lld ",(f[i]%mod+mod)%mod);
    	return 0;
    }
    
    

  • 相关阅读:
    appium连接夜游神的方法
    selenium自动化测试配置工具整理
    appium+Python 启动app(三)登录
    appium+Python 启动app(二)
    appium+Python 启动app(一)
    appium滑动操作(向上、向下、向左、向右)
    【Prometheus专题】4. Prometheus通过webhook集成Slack或Mattermost发送告警
    【Prometheus专题】3. Prometheus监控组件的配置
    【Prometheus专题】2. Prometheus监控组件安装
    【Prometheus专题】1. Prometheus介绍(何为Prometheus)
  • 原文地址:https://www.cnblogs.com/GavinZheng/p/12037097.html
Copyright © 2011-2022 走看看