震驚!FFT竟然還能這麼寫?活到這麼大沒見過這麼寫FFT的!

模板題

洛谷P3803 【模板】多項式乘法(FFT)

在這裏插入圖片描述

點值表達

大莉模擬的複雜度是O(n2)O(n^2)
引入點值表達qwq(這部分摘抄自毒瘤czh的FFT講稿):
1.1.例子:A(x)=x2+2x1A(x)=x^2+2x-1可以被表達爲{(0,1),(1,2),(2,7)}\{(0 , -1), (1 , 2),(2 , 7)\}

2.2.用處:在多項式乘法中,係數表達暴力模擬複雜度O(n2)O(n^2),但是點值表達式可以O(n)O(n)求出

更加具體地說,我們有兩個點值表達
A(x)={(x0,A(x0)),(x1,A(x1)),(x2,A(x2)),....,(xn,A(xn))}A(x)=\{(x_0,A(x_0)),(x_1,A(x_1)),(x_2,A(x_2)),....,(x_n,A(x_n))\}
B(x)={(x0,B(x0)),(x1,B(x1)),(x2,B(x2)),....,(xn,B(xn))}B(x)=\{(x_0,B(x_0)),(x_1,B(x_1)),(x_2,B(x_2)),....,(x_n,B(x_n))\}
乘積C(x)=A(x)×B(x)={(x0,A(x0)×B(x0)),(x1,A(x1)×B(x1)),(x2,A(x2)×B(x2)),....,(xn,A(xn)×B(xn))}C(x)=A(x)\times B(x)=\{(x_0,A(x_0)\times B(x_0)),(x_1,A(x_1)\times B(x_1)),(x_2,A(x_2)\times B(x_2)),....,(x_n,A(x_n)\times B(x_n))\}
所以我們可以通過O(n)O(n)複雜度,求出兩個點值表達式乘積的結果(兩個多項式的乘積又稱卷積)
知道了點值表達式就珂以確定一個多項式了qwq(珂以列方程用高斯消元爆解一波qwq

其中DFT算法是暴力O(n2)O(n^2)把係數表達轉點值表達,FFT算法是O(nlogn)O(nlogn)把係數表達轉點值表達

DFT(離散傅里葉變換)

普通做法:隨機取nn個值代入求值qwq。
DFT是考慮建立一個複平面,在上面懟出 畫一個單位圓:

因爲是單位圓,所以圓上的每一個點對應的複數模均爲1。
把這個圓平均分成nn份,把每個點對應的複數代入。
具體地說,這個函數的點值表達是{(wn0,f(wn0)),(wn1,f(wn1)),...,(wnn1,f(wnn1))}\{(w_n^0,f(w_n^0)),(w_n^1,f(w_n^1)),...,(w_n^{n-1},f(w_n^{n-1}))\}
其中wnk=cos(kn2π)+isin(kn2π)w_n^k=cos(\frac{k}{n}2\pi)+isin(\frac{k}{n}2\pi)

FFT(快速傅里葉變換)

ww的性質

證明珂以看巨神mhy的博客qwq
這裏只列出性質,就不證明了:
1.wnk=w2n2k1.w_n^k=w_{2n}^{2k}
2.wnk+n2=wnk2.w_n^{k+\frac{n}{2}}=-w_n^k
3.wn0=wnn=13.w_n^0=w_n^n=1
4.wnpwnq=wnp+q4.w_n^p*w_n^q=w_n^{p+q}

表達式的變形

首先把一個函數用係數表達式寫出來(假設nn爲偶數,如果不是就補一個0xn0*x^n):
f(x)=a0+a1x1+a2x2+a3x3+a4x4+...+an1xn1f(x)=a_0+a_1x^1+a_2x^2+a_3x^3+a_4x^4+...+a_{n-1}x^{n-1}
按照下標的奇偶性把ff分成兩半:
f(x)=(a0+a2x2+...+an2xn2)+x(a1+a3x2+...+an1xn2)f(x)=(a_0+a_2x^2+...+a_{n-2}x^{n-2})+x(a_1+a_3x^2+...+a_{n-1}x^{n-2})
A1(x)=a0+a2x+a4x2+...+an2xn22A_1(x)=a_0+a_2x+a_4x^2+...+a_{n-2}x^{\frac{n-2}{2}}A2(x)=a1+a3x+a5x2+...+an1xn22A_2(x)=a_1+a_3x+a_5x^2+...+a_{n-1}x^{\frac{n-2}{2}}
不難發現f(x)=A1(x2)+xA2(x2)f(x)=A_1(x^2)+xA_2(x^2)

以下分別把wnkw_n^kwnk+n2w_n^{k+\frac{n}{2}}代入:
wnkw_n^k代入,得到f(wnk)=A1(wnkwnk)+xA2(wnkwnk)=A1(wn2k)+wnkA2(wn2k)f(w_n^k)=A_1(w_n^k*w_n^k)+xA_2(w_n^k*w_n^k)=A_1(w_n^{2k})+w_n^kA_2(w_n^{2k})
ww的性質1,珂以得到:
f(wnk)=A1(wn2k)+wnkA2(wn2k)f(w_n^k)=A_1(w_{\frac{n}{2}}^k)+w_n^kA_2(w_{\frac{n}{2}}^k)

wnk+n2w_n^{k+\frac{n}{2}}代入,得到:
f(wnk+n2)=A1(wn2k+n)+wnk+n2A2(wn2k+n)=A1(wn2kwnn)wnkA2(wn2kwnn)=A1(wn2k)wnkA2(wn2k)f(w_n^{k+\frac{n}{2}})=A_1(w_n^{2k+n})+w_n^{k+\frac{n}{2}}A_2(w_n^{2k+n})=A_1(w_n^{2k}*w_n^n)-w_n^kA_2(w_n^{2k}*w_n^n)=A_1(w_n^{2k})-w_n^kA_2(w_n^{2k})

分治寫法

認真觀察……這f(wnk)f(w_n^k)f(wnk+n2)f(w_n^{k+\frac{n}{2}})長得很像?
於是珂以遞歸(分治)解決,每次求解左半部分的答案,再推出右半部分的答案:

void FFT(complex<double> *ans,int n,int type) {
	//type=1表示FFT;type=-1表示IFFT 
	if(n==1)	return;
	//偶數存L,奇數存R 
	complex<double> L[(n>>1)+1],R[(n>>1)+1];
	for(re i=0; i<n; i+=2) {
		L[i>>1]=ans[i];
		R[i>>1]=ans[i+1];
	}
	FFT(L,n>>1,type);
	FFT(R,n>>1,type);
	//玄學單位圓亂搞 
	complex<double> w(1,0);
	complex<double> k(cos(2.0*pi/n),type*sin(2.0*pi/n));
	int maxn=n>>1;
	for(re i=0; i<maxn; i++) {
		complex<double> tmp=w*R[i];
		ans[i]=L[i]+tmp;
		ans[i+maxn]=L[i]-tmp;
		w*=k;
	}
}

然後交到例題上,TLE??
遞歸常數蜃大qwq,考慮使用迭代。

迭代寫法

觀察下面這張圖:

發現原序列的每個數按對稱軸翻轉一下就是後序列的數qwq,稱之爲位逆序置換。
所以,我們可以預處理出係數位置遞歸到最底層的位置,自底向上進行遞推。
處理出位逆序置換之後的方法(R[i]R[i]表示ii進行位逆序置換之後得到的值,LL表示二進制下的總位數):

for(re i=0; i<MAXN; i++) {		//MAXN表示要處理出來的數的總數
	//表示先把最後一位去掉,剩下的部分進行反轉,然後把最後一位反轉到最高位qwq
	R[i]=(R[i>>1]>>1)|((i&1)<<(L-1));
}

所以把遞歸強行用循環寫出來:
第一維枚舉區間長度,即遞歸版本中的nn。第二維枚舉區間開始位置。第三維枚舉當前數是區間中的第幾個。
然後珂以大莉寫一波qwq(我的版本中,區間長度實際上爲2i2i):

void FFT(Complex *ans,int n,int type) {
	//type=1表示FFT,type=-1表示IFFT 
	//把序列交換成後序列的形式 
	for(re i=0; i<n; i++)	if(i<R[i])	swap(ans[i],ans[R[i]]);
	for(re i=1; i<n; i<<=1) {
		//(2*pi)/(2*i)=pi/i
		Complex wn=(Complex){cos(pi/(double)i),type*sin(pi/(double)i)};
		for(re j=0; j<n; j+=(i<<1)) {
			Complex w=(Complex){1,0};
			for(re k=0; k<i; k++) {
				//類似遞歸版的處理
				const Complex x=ans[j+k],y=w*ans[i+j+k];
				ans[j+k]=x+y;
				ans[i+j+k]=x-y;
				w=w*wn;
			}
		}
	}
}

IFFT(快速傅里葉逆變換)

毒瘤czh:

美妙結論:一個多項式在分治的過程中乘上單位根的共軛複數,分治完的每一項除以n即爲原多項式的每一項係數(具體看程序實現,證明略)

也就是在調用的時候把type設成-1就珂以了

例題完整代碼

至此,就珂以寫出例題了(注意預處理和邊界條件qwq):

#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<math.h>
#define re register int
#define rl register ll
#define pi 3.14159265358979323846264338328
using namespace std;
int read() {
    re x=0,f=1;
    char ch=getchar();
    while(ch<'0' || ch>'9') {
        if(ch=='-')    f=-1;
        ch=getchar();
    }
    while(ch>='0' && ch<='9') {
        x=10*x+ch-'0';
        ch=getchar();
    }
    return x*f;
}
inline void write(const int x) {
	if(x>9)	write(x/10);
	putchar(x%10+'0');
}
const int Size=4000005;
//手寫complex類,不過貌似也沒快多少 
struct Complex {
    double x,y;
    inline double real() {
        return x;
    }
};
inline Complex operator + (const Complex a,const Complex b) {
    return (Complex){a.x+b.x,a.y+b.y};
}
inline Complex operator - (const Complex a,const Complex b) {
    return (Complex){a.x-b.x,a.y-b.y};
}
inline Complex operator * (const Complex a,const Complex b) {
    return (Complex){a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x};
}
Complex A[Size],B[Size];
int R[Size];
void FFT(Complex *ans,const int n,const int type) {
	//type=1表示FFT,type=-1表示IFFT
	for(re i=0; i<n; i++)	if(i<R[i])	swap(ans[i],ans[R[i]]);
    for(re i=1; i<n; i<<=1) {
    	//(2*pi)/(2*i)=pi/i
        const Complex wn=(Complex){cos(pi/i),type*sin(pi/i)};
        for(re j=0; j<n; j+=i<<1) {
            Complex w=(Complex){1,0};
            for(re k=0; k<i; k++) {
                Complex x=ans[j+k],y=w*ans[i+j+k];
                ans[j+k]=x+y;
                ans[i+j+k]=x-y;
                w=w*wn;
            }
        }
    }
}
int ans[Size];
int main() {
    int n=read();
    int m=read();
    for(re i=0; i<=n; i++) {
        A[i]=(Complex){read(),0};
    }
    for(re i=0; i<=m; i++) {
    	B[i]=(Complex){read(),0};
	}
	//now爲大於n+m的第一個2的冪,因爲總區間長度必須是2的冪qwq
    int now=1,L=0;
    while(now<=n+m) {
		now<<=1;
		L++;
	}
    for(re i=0; i<now; i++) {
    	R[i]=(R[i>>1]>>1)|((i&1)<<(L-1));
	}
    FFT(A,now,1);
    FFT(B,now,1);
    for(re i=0; i<=now; i++) {
        A[i]=A[i]*B[i];
    }
    FFT(A,now,-1);
    for(re i=0; i<=n+m; i++) {
		write(int(A[i].real()/now+0.5));	//注意四捨五入
		putchar(' ');
    }
    return 0;
}

其他題目

洛谷P1919 A*B Problem升級版
bzoj2179 (權限題)快速傅里葉變換

bzoj2194(權限題)快速傅里葉變換之二

bzoj3527 力

bzoj4827 禮物
……
…………
………………
題解?咕咕咕

還是有一篇的qwq

bzoj4332(權限題)
洛谷P5075
題解傳送門

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章