1. 程式人生 > >【快速傅立葉變換】【FFT】【WikiOI】【P3132】【高精度練習之超大整數乘法】

【快速傅立葉變換】【FFT】【WikiOI】【P3132】【高精度練習之超大整數乘法】

FFT,快速傅立葉變換,蒟蒻看別人的題解都太深奧,看不懂,好不容易學會,以蒟蒻的理解寫給那些想學FFT卻又找不到合適的資料的OIer,蒟蒻理解有限,難免有許多錯誤,請大家多多包涵。

快速傅立葉變換

百度的各種講解都TM扯什麼頻率什麼的,蒟蒻完全看不懂,後來認真看了看算導,獲益匪淺,算導上講的真心不賴,有很多內容都來自算導。

1.多項式

        多項式的兩種表達方式:係數表達和點值表達

係數表達就是大家常用的表達方式,點值表達就像在這個多項式函式上取n個不同的點,這樣就可以確定原多項式。

比如說二次函式需要3個點就可以確定,一次函式需要2個點,一個n次多項式需要n個點(n次多項式意思是有0..n-1次冪的多項式)

A(x)=x^2+2*x-1可以被表達為{  ( 0 , -1 ) , ( 1 , 2 ) , ( 2 , 7 )  }

加法和乘法:

        

B(x)=x^2-x+2  { ( 0 , 2 ) , ( 1 , 2 ) , ( 2 , 4 ) }

C(x)=A(x)+B(x)=2x^2+x+1   { ( 0, 1) , ( 1 , 4 ) , ( 2, 11 ) }

     注意乘法需要2n個點 lz比較懶就不寫了……

於是我們得到一個計算多項式的方法:

 2.n次單位複數根

     

有關複數根的性質可以百度到,不再贅述

3.DFT&&FFT

使用單位根計算點值表示式叫DFT(離散傅立葉變換)複雜度n^2,FFT是其優化版複雜度nlogn


計算FFT的虛擬碼(好吧用的是python的高亮)

下劃線代表的是下標,括號代表上標,for 迴圈的range是左閉右開的

FFT(a):
	n=a.length()
	if n==1:
		return a
	w_n=e^(pi*i/n)=complex(cos(2*pi/n),sin(2*pi/n))
	w=1
	a(0)=[a0,a2,...a_n-2]
	a(1)=[a1,a3,...a_n-1]
	y(0)=FFT(a(0))
	y(1)=FFT(a(1))
	for k in range(0,n/2):
		y_k=y_k(0)+w*y_k(1)
		y_k+n/2=y_k(0)-w*y_k(1)
		w=w*w_n
	return y

4.遞迴=>迭代??

FFT的for迴圈中有兩次w_n^k*y_k(1)的計算,於是可以改寫成這樣

for k in range(0,n/2):
	t=w*y_k(1)
	y_k=y_k(0)+t
	y_k+n/2=y_k(0)-t
	w=w*w_n
#這一過程被稱蝴蝶操作

觀察每次按照奇偶位置分割所形成的樹:

每個數和他二進位制相反的位置互換!!

虛擬碼(算導給的真是……)

BIT-REVERSE-COPY(a,A):
	n=a.length()
	for k in range(0,n):
		A[rev(k)]=a_k
#算導說rev函式很好寫,就沒寫……

於是我們給出FFT的迭代實現的虛擬碼:
FFT(a):
	BIT-REVERSE-COPY(a,A)
	n=a.length()
	for s in range(1,log2(n)+1):
		m=2^s
		w_m=e^(2*pi*i/m)=complex(cos(2*pi*m),sin(2*pi*m))
		for k in range(0,n,m):
			w=1
			for j in range(0,m/2):
				t=w*A[k+j+m/2]
				u=A[k+j]
				A[k+j]=u+t
				A[k+j+m/2]=u-t
				w=w*w_m
	return A
差不多講完了,最後給出C++程式碼,有一大部分是lz借鑑別人的Code,以後附上地址
#include<bitset>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define N 400005
#define pi acos(-1.0) // PI值
using namespace std;
struct complex
{
	double r,i;
	complex(double real=0.0,double image=0.0){
		r=real;	i=image;
	}
	// 以下為三種虛數運算的定義
	complex operator + (const complex o){
		return complex(r+o.r,i+o.i);
	}
	complex operator - (const complex o){
		return complex(r-o.r,i-o.i);
	}
	complex operator * (const complex o){
		return complex(r*o.r-i*o.i,r*o.i+i*o.r);
	}
}x1[N],x2[N];
char a[N/2],b[N/2];
int sum[N]; // 結果存在sum裡
int vis[N];
void brc(complex *a,int l){//原來神犇的二進位制平攤反轉置換太神看不懂,蒟蒻寫了一個O(n)的…… 
	memset(vis,0,sizeof(vis));//O(logn)的在後面 
	for(int i=1;i<l-1;i++){
		int x=i,y=0;
		int m=(int)log2(l)+0.1;
		if(vis[x])continue;
		while(m--){
			y<<=1;
			y|=(x&1);
			x>>=1;
		}
		vis[i]=vis[y]=1;
		swap(a[i],a[y]);
	}	
}
void fft(complex *y,int l,double on) // FFT O(nlogn)
				     		// 其中on==1時為DFT,on==-1為IDFT
{
	register int h,i,j,k;
	complex u,t; 
	brc(y,l); // 呼叫反轉置換
	for(h=2;h<=l;h<<=1) // 控制層數
	{
		// 初始化單位復根
		complex wn(cos(on*2*pi/h),sin(on*2*pi/h));
		for(j=0;j<l;j+=h) // 控制起始下標
		{
			complex w(1,0); // 初始化螺旋因子
			for(k=j;k<j+h/2;k++) // 配對
			{
				u=y[k];
				t=w*y[k+h/2];
				y[k]=u+t;
				y[k+h/2]=u-t;
				w=w*wn; // 更新螺旋因子
			} // 據說上面的操作叫蝴蝶操作…
		}
	}
	if(on==-1)	for(i=0;i<l;i++)	y[i].r/=l; // IDFT
}
/* 
void fft2(complex *a,int s,int t){//蒟蒻自己寫的遞迴版FFT,不保證正確 ,程式碼內部有未定義變數 
	if((n>>t)==1)return;//s記錄起始,t記錄深度,呼叫時應從0開始 
	fft(a,s,t+1);
	fft(a,s+(1<<t),t+1);
	for(int i=0;i<(n>>(t+1));i++){
		p=(i<<(t+1))+s;
		wt=w[i<<t]*a[p+(1<<t)];
		tt[i]=a[p]+wt;
		tt[i+(n>>(t+1))]=a[p]-wt;
	}
	for(i=0;i<(n>>t);i++)a[(i<<t)+s]=tt[i];
}*/
int main(void)
{
	int l1,l2,l;
	register int i;
	while(scanf("%s%s",a,b)!=EOF)
	{
		l1=strlen(a);
		l2=strlen(b);
		l=1;
		while(l<l1*2 || l<l2*2)	l<<=1; // 將次數界變成2^n
					      				// 配合二分與反轉置換
		for(i=0;i<l1;i++) // 倒置存入
		{
			x1[i].r=a[l1-i-1]-'0';
			x1[i].i=0.0;
		}
		for(;i<l;i++)	x1[i].r=x1[i].i=0.0;
		// 將多餘次數界初始化為0
		for(i=0;i<l2;i++)
		{
			x2[i].r=b[l2-i-1]-'0';
			x2[i].i=0.0;
		}
		for(;i<l;i++)	x2[i].r=x2[i].i=0.0;
		fft(x1,l,1); // DFT(a)
		fft(x2,l,1); // DFT(b)
		for(i=0;i<l;i++)	x1[i]=x1[i]*x2[i]; // 點乘結果存入a
		fft(x1,l,-1); // IDFT(a*b)
		for(i=0;i<l;i++)	sum[i]=x1[i].r+0.5; // 四捨五入
		for(i=0;i<l;i++) // 進位
		{
			sum[i+1]+=sum[i]/10;
			sum[i]%=10;
		}
		l=l1+l2-1;
		while(sum[l]<=0 && l>0)	l--; // 檢索最高位
		for(i=l;i>=0;i--)	putchar(sum[i]+'0'); // 倒序輸出
		putchar('\n');
	}
	return 0;
}
/*void brc(complex *y,int l) // 二進位制平攤反轉置換 O(logn)
{
	register int i,j,k;
	for(i=1,j=l/2;i<l-1;i++)
	{
		if(i<j)	swap(y[i],y[j]); // 交換互為下標反轉的元素
								// i<j保證只交換一次
		k=l/2;
		while(j>=k) // 由最高位檢索,遇1變0,遇0變1,跳出
		{
			j-=k;
			k>>=1;
		}
		if(j<k)	j+=k;
	}
}*/

pyc神犇的寫法,bzoj3527,zjoi2014 力,無限YM

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=1000010;
int n,N,L;
int rev[maxn];
int dig[maxn];
double p[maxn];
struct cp{
	double r,i;
	cp(double _r=0,double _i=0):
		r(_r),i(_i){}
	cp operator+(cp x){return cp(r+x.r,i+x.i);}
	cp operator-(cp x){return cp(r-x.r,i-x.i);}
	cp operator*(cp x){return cp(r*x.r-i*x.i,r*x.i+i*x.r);}
};
cp a[maxn],b[maxn],c[maxn],A[maxn],x,y;
void FFT(cp a[],int flag){
	for(int i=0;i<N;i++)A[i]=a[rev[i]];
	for(int i=0;i<N;i++)a[i]=A[i];
	for(int i=2;i<=N;i<<=1){
		cp wn(cos(2*M_PI/i),flag*sin(2*M_PI/i));
		for(int k=0;k<N;k+=i){
			cp w(1,0);
			for(int j=0;j<i/2;j++){
				x=a[k+j];
				y=w*a[k+j+i/2];
				a[k+j]=x+y;
				a[k+j+i/2]=x-y;
				w=w*wn;  
			}
		}
	}
	if(flag==-1)for(int i=0;i<N;i++)a[i].r/=N;
}
double anss[maxn];
int main(){
	scanf("%d",&n);
	for(int i=0;i<n;i++)scanf("%lf",&p[i]);
	for(L=0,N=1;N<n;N<<=1,L++);L++;N<<=1;
	for(int i=0;i<N;i++){
		int len=0;
		for(int t=i;t;t>>=1)dig[len++]=t&1;
		for(int j=0;j<L;j++)rev[i]=rev[i]*2+dig[j];
	}
	for(int i=0;i<n;i++)a[i]=cp(p[i],0);
	for(int i=1;i<n;i++)b[i]=cp(1.0/i/i,0);
	FFT(a,1);FFT(b,1);
	for(int i=0;i<N;i++)c[i]=a[i]*b[i];
	FFT(c,-1);
	for(int i=0;i<n;i++)anss[i]=c[i].r;
	memset(a,0,sizeof(a));
	memset(b,0,sizeof(b));
	for(int i=0;i<n;i++)a[i]=cp(p[n-i-1],0);
	for(int i=1;i<n;i++)b[i]=cp(1.0/i/i,0);
	FFT(a,1);FFT(b,1);
	for(int i=0;i<N;i++)c[i]=a[i]*b[i];
	FFT(c,-1);
	for(int i=0;i<n;i++)anss[i]-=c[n-i-1].r;
	for(int i=0;i<n;i++)
		printf("%.9f\n",anss[i]);
	return 0;
}


重新過了一遍高精乘

#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=1e6+10;
struct cp{
	double r,i;
	cp(double _r=0,double _i=0):
		r(_r),i(_i){}
	cp operator+(cp x){return cp(r+x.r,i+x.i);}
	cp operator-(cp x){return cp(r-x.r,i-x.i);}
	cp operator*(cp x){return cp(r*x.r-i*x.i,r*x.i+i*x.r);}
};
cp a[maxn],b[maxn],A[maxn],x,y,c[maxn];
char s1[maxn],s2[maxn];
int sum[maxn],a1[maxn],a2[maxn],dig[maxn];
int len1,len2,rev[maxn],N,L;
void FFT(cp a[],int flag){
	for(int i=0;i<N;i++)A[i]=a[rev[i]];
	for(int i=0;i<N;i++)a[i]=A[i];
	for(int i=2;i<=N;i<<=1){
		cp wn(cos(2*M_PI/i),flag*sin(2*M_PI/i));
		for(int k=0;k<N;k+=i){
			cp w(1,0);
			for(int j=k;j<k+i/2;j++){
				x=a[j];
				y=a[j+i/2]*w;
				a[j]=x+y;
				a[j+i/2]=x-y;
				w=w*wn;
			}
		}
	}
	if(flag==-1)for(int i=0;i<N;i++)a[i].r/=N;
}
int main(){
	scanf("%s%s",s1,s2);
	len1=strlen(s1);
	len2=strlen(s2);
	for(N=1,L=0;N<max(len1,len2);N<<=1,L++);N<<=1;L++;
	for(int i=0;i<N;i++){
		int len=0;
		for(int t=i;t;t>>=1)dig[len++]=t&1;
		for(int j=0;j<L;j++)rev[i]=(rev[i]<<1)|dig[j];
	}
	for(int i=0;i<len1;i++)a1[len1-i-1]=s1[i]-'0';
	for(int i=0;i<len2;i++)a2[len2-i-1]=s2[i]-'0';
	for(int i=0;i<N;i++)a[i]=cp(a1[i]);
	for(int i=0;i<N;i++)b[i]=cp(a2[i]);
	FFT(a,1);FFT(b,1);
	for(int i=0;i<N;i++)c[i]=a[i]*b[i];
	FFT(c,-1);
	for(int i=0;i<N;i++)sum[i]=c[i].r+0.5;
	for(int i=0;i<N;i++){
		sum[i+1]+=sum[i]/10;
		sum[i]%=10;
	}
	int l=len1+len2-1;
	while(sum[l]==0&&l>0)l--;
	for(int i=l;i>=0;i--)
	putchar(sum[i]+'0');
	putchar('\n');
	return 0;
}


相關推薦

快速變換FFTWikiOIP3132精度練習超大整數乘法

FFT,快速傅立葉變換,蒟蒻看別人的題解都太深奧,看不懂,好不容易學會,以蒟蒻的理解寫給那些想學FFT卻又找不到合適的資料的OIer,蒟蒻理解有限,難免有許多錯誤,請大家多多包涵。 快速傅立葉變換 百度的各種講解都TM扯什麼頻率什麼的,蒟蒻完全看不懂,後來認真看了看算導

1449: 快速變換(模版題)多項式乘法

時間限制: 1 Sec 記憶體限制: 256 MB 提交: 44 解決: 14 [提交][狀態][討論版] 題目描述 【題意】 給你兩個多項式,請輸出乘起來後的多項式。 【輸入】 第一

知識總結快速變換FFT

這可能是我第五次學FFT了……菜哭qwq 先給出一些個人認為非常優秀的參考資料: 一小時學會快速傅立葉變換(Fast Fourier Transform) - 知乎 小學生都能看懂的FFT!!! - 胡小兔 - 部落格園 快速傅立葉變換(FFT)用於計算兩個\(n\)次多項式相乘,能把複雜度從樸素的\

演算法快速變換FFT)的遞迴實現

FFT是數字訊號處理中的重要演算法,在matlab中可以直接呼叫fft()函式,本文是C++版的FFT演算法,用遞迴方式進行實現。 //================================== //Signal_Process_FFT_v1.cpp //====

模板快速變換FFT)(BZOJ2179)

Description 給出兩個n位10進位制整數x和y,你需要計算x*y。 Input 第一行一個正整數n。 第二行描述一個位數為n的正整數x。 第三行描述一個位數為n的正整數y。 Output 輸出一行,即x*y的結果。 Code #include<cstdio> #

OI向快速變換(Fast Fourier Transform)

## 【OI向】快速傅立葉變換(Fast Fourier Transform) ### FFT的作用 ​ 在學習一項演算法之前,我們總該關心這個演算法究竟是為了幹什麼。 ​ (以下應用只針對OI) ​ 一句話:求多項式乘法(當然它的實際用處很多) ​ 設多項式 ​ $A(x)=a_0+

演算法學習筆記快速變換

# 快速傅立葉變換 快速傅立葉變換(Fast Fourier Transform, FTT)在ACM/OI中最主要的應用是計算多項式乘法。 ## 多項式的係數表示和點值表示 假設$f(x)$為$x$的$n$階多項式,則其可以表示為: $$f(x)=\sum_{i=0}^na_ix^i$$ 這裡的$n

caioj1450:快速變換整數乘法

rose clas scan name 代碼 printf 答案 r+ 傅裏葉變換 【傳送門:caioj1450】 簡要題意:   給出兩個超級大的整數,求出a*b 題解:   Rose_max出的一道FFT例題,卡掉高精度 = =   只要把a和b的每一

快速變換FFT模板

return namespace double names http ++ main swap pre 遞歸版 UOJ34多項式乘法 //容易暴棧,但是很好理解 #include <cmath> #include <iostream> #includ

快速變換FFT)及其應用

有一個 swap max read mes turn scan 原本 color 在信息學競賽中FFT只有一個用處那就是加速多項式的乘法 多項式乘法原本的時間復雜度是O(n^2)的,然後經過FFT之後可以優化為O(nlogn) FFT就是將系數表示法轉化成點值表示法相乘,再

快速變換FFT的學習筆記一:C語言程式碼的簡單實現

快速傅立葉變換FFT的學習筆記一:C語言程式碼的簡單實現 fft.c #include "math.h" #include "fft.h" void conjugate_complex(int n,complex in[],complex out[]) { int i = 0

離散變換(DFT)和快速變換FFT)原理與實現

目錄 1、影象變換 2、離散傅立葉變換(Discrete Fourier Transform) 3、DFT性質 4、DFT與數字影象處理 5、FFT-快速傅立葉變換 6、DFT與FFT的演算法實現 1. 影象變換 — —數學領域中有很多種變換,如傅立葉變換、拉普拉斯變

使用Apache commons-maths3-3.6.1.jar包實現快速變換(java)

    快速傅立葉變換 (fast Fourier transform), 即利用計算機計算離散傅立葉變換(DFT)的高效、快速計算方法的統稱,簡稱FFT。   1 package com; 2 3 import org.apache.commons.math3

快速變換FFT(模板)

轉載出處 https://blog.csdn.net/f_zyj/article/details/76037583 摘自大佬的部落格 FFT(最詳細最通俗的入門手冊) const double PI=acos(-1.0); // 複數結構體 struct Complex { dou

「學習筆記」Fast Fourier Transform 快速變換

快速傅立葉變換( Fast Fourier Transform,FFT \text{Fast Fourier Transfo

FFT快速變換

- 概念引入   - 點值表示     對於一個$n - 1$次多項式$A(x)$,可以通過確定$n$個點與值(即$x$和$y$)來表示這唯一的$A(x)$   - 複數     對於一元二次方程     $$x^2 + 1 = 0$$     在實數範圍內無解,那麼我們將實數範圍擴充,就得到了複數,

基於python的快速變換FFT(二)

基於python的快速傅立葉變換FFT(二)本文在上一篇部落格的基礎上進一步探究正弦函式及其FFT變換。 知識點  FFT變換,其實就是快速離散傅立葉變換,傅立葉變換是數字訊號處理領域一種很重要的演算法。要知道傅立葉變換演算法的意義,首先要了解傅立葉原理的意義。傅立葉原理表明:任何連續測量的時序或訊號,都可

[學習筆記]FFT——快速變換

大力推薦部落格: 傅立葉變換(FFT)學習筆記   一、多項式乘法:   我們要明白的是:FFT利用分治,處理多項式乘法,達到O(nlogn)的複雜度。(雖然常數大)FFT=DFT+IDFTDFT:本質是把多項式的係數表達轉化為點值表達。因為點值表達,y可以直接相乘。點值表達下相

快速變換FFT的學習筆記二:深入實踐

快速傅立葉變換FFT的學習筆記二:深入實踐 快速傅立葉變換(Fast Fourier Transform)是離散傅立葉變換的一種快速演算法,簡稱FFT,通過FFT可以將一個訊號從時域變換到頻域。 資料結構 通過AD採集到一串時域上的資料點,一個int型的陣列

5.6.1 快速變換FFT+RFFT)

1.影象頻域處理的意義        在影象處理和分析中,經常會將影象從影象空間轉換到其他空間中,並利用這些空間的特點進行對轉換後圖像進行分析處理,然後再將處理後的影象轉換到影象空間中,這稱之為影象變換。 在一些影象處理和分析中通過空間變換往往會取得更有效