模板題
點值表達
大莉模擬的複雜度是。
引入點值表達qwq(這部分摘抄自毒瘤czh的FFT講稿):
例子:可以被表達爲
用處:在多項式乘法中,係數表達暴力模擬複雜度,但是點值表達式可以求出
更加具體地說,我們有兩個點值表達
乘積
所以我們可以通過複雜度,求出兩個點值表達式乘積的結果(兩個多項式的乘積又稱卷積)
知道了點值表達式就珂以確定一個多項式了qwq(珂以列方程用高斯消元爆解一波qwq)
其中DFT算法是暴力把係數表達轉點值表達,FFT算法是把係數表達轉點值表達
DFT(離散傅里葉變換)
普通做法:隨機取個值代入求值qwq。
DFT是考慮建立一個複平面,在上面懟出 畫一個單位圓:
因爲是單位圓,所以圓上的每一個點對應的複數模均爲1。
把這個圓平均分成份,把每個點對應的複數代入。
具體地說,這個函數的點值表達是,
其中
FFT(快速傅里葉變換)
的性質
證明珂以看巨神mhy的博客qwq
這裏只列出性質,就不證明了:
表達式的變形
首先把一個函數用係數表達式寫出來(假設爲偶數,如果不是就補一個):
按照下標的奇偶性把分成兩半:
令,
不難發現。
以下分別把和代入:
把代入,得到
由的性質1,珂以得到:
把代入,得到:
分治寫法
認真觀察……這和長得很像?
於是珂以遞歸(分治)解決,每次求解左半部分的答案,再推出右半部分的答案:
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,稱之爲位逆序置換。
所以,我們可以預處理出係數位置遞歸到最底層的位置,自底向上進行遞推。
處理出位逆序置換之後的方法(表示進行位逆序置換之後得到的值,表示二進制下的總位數):
for(re i=0; i<MAXN; i++) { //MAXN表示要處理出來的數的總數
//表示先把最後一位去掉,剩下的部分進行反轉,然後把最後一位反轉到最高位qwq
R[i]=(R[i>>1]>>1)|((i&1)<<(L-1));
}
所以把遞歸強行用循環寫出來:
第一維枚舉區間長度,即遞歸版本中的。第二維枚舉區間開始位置。第三維枚舉當前數是區間中的第幾個。
然後珂以大莉寫一波qwq(我的版本中,區間長度實際上爲):
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 (權限題)快速傅里葉變換
bzoj4827 禮物
……
…………
………………
題解?咕咕咕
還是有一篇的qwq