顯然因為我不會數學,所以這篇文章會非常「 感性 」。
將兩個多項式乘起來,即求 \(f*g=h\) 。多項式的項數 \(n\le10^5\)。
複數複數是指形如 \(x+yi\) 的數,高中會教。
它的四則運算法則是這樣的:(令 \(p,q\) 為兩個複數)
\[p\pm q=(x_p\pm x_q)+(y_p\pm y_q)i\\
p\times q=(x_px_q-y_py_q)+(x_py_q+x_qy_p)i
\]除法用不著。
所以**是這樣的:(建議不要用 stl 的複數,常數巨大)
struct mlea[n7],b[n7];
mle operator + (mle p,mle q);}
mle operator - (mle p,mle q);}
mle operator * (mle p,mle q);}
多項式運算
略。點值表達法
選出 \((2n-1)\) 個互不相同的橫座標 \(x_i\) ,代入 \(f\) 與 \(g\) 中,得到很多個 \(fy_i,gy_i\),而 \((x_i,fy_i)\) 就是 \(f\) 的點值表示式, \((x_i,gy_i)\) 就是 \(g\) 的點值表示式。神奇的事實是, \((x_i,fy_i\times gy_i)\) 就是 \(h\) 的點值表示式!
!所以fft的思想就是,將 \(f\) 和 \(g\) 轉換成點值表達法,然後相乘得到 \(h\),最後再化為係數表示法(普通多項式的表示)。
其中,轉化為點值表達法的步驟叫做dft,化回來的步驟叫做idft。
單位根有乙個神奇的東西叫做單位根(複數),滿足 \(w^n=1\) 的 \(w\) 被稱作 \(n\) 次單位根。(事實上應該是 \(\omega\),但是寫起來太麻煩了就用 \(w\) 了)
經過推導,如果將所有的單位根排列,第 \(k\) 個 \(n\) 次單位根 \(\large w_k=e^}\)
設 \(n\) 是正偶數,且 \(m\) 是 \(n\) 的一半,那麼有 \((w_n^k)^2=w_m^k\) 以及 \(w_n^=-w-n^k\)。這兩個等式就是法術,下文會用到。
dft(轉成點值表示)
首先為了方便,我們把多項式變為 \(n=(2^k-1)\) 次多項式(不足的補係數 \(0\)),而且它大於原本的 \(f\) 和 \(g\) 的項數之和。這樣原來的 \((2n-1)\) 就不大於的 \(n\) ,方便統計。
然後我們想知道點值表達,所以我們需要代入一些 \(x\),並求出 \(f(x)\) 的值。
我們選擇把魔幻的單位根, \(w_n^0,w_n^1...w_n^\) 代入。
好,我們怎麼求 \(f(x)\)?先變變形式:
\[f(x)=a_0x^0+a_1x^1+a_2x^2+...+a_x^\\
f(x)=(a_0x^0+a_2x^2+a_4x^4+...)+(a_1x^1+a_3x^3+a_5x^5+...)\\
f(x)=(a_0x^0+a_2x^2+a_4x^4+...)+x(a_1x^0+a_3x^2+a_5x^4+...)\\
f(x)=f_0(x^2)+xf_1(x^2)
\]其中 \(f,f_0,f_1\) 並不是一樣的,注意,\(f_0\) 的係數依次為 \(a_0,a_2,a_4...\) ,\(f_1\) 為 \(a_1,a_3,a_5...\)
運用著前文提到的法術,仍然設 \(m=\frac\),對於 \(k有
\[代入:f(w_n^k)=f_0((w_n^k)^2)+(w_n^k)f_1((w_n^k)^2)\\
等式一:f(w_n^k)=f_0(w_m^k)+w_n^kf_1(w_m^k)
\]那麼對於 \(k\ge m\) 呢?我們可以說它是 \((k+m)\) 且 \(k。繼續用法術:
\[代入:f(w_n^)=f_0((w_n^)^2)+(w_n^)f_1((w_n^)^2)\\
等式二:f(w_n^)=f_0((w_n^k)^2)+-w_n^kf_1((w_n^k)^2)\\
等式一:f(w_n^)=f_0(w_m^k)-w_n^kf_1(w_m^k)
\]哇塞,乙個是 \(f_0+f_1\),另乙個是 \(f_0-f_1\)。
於是我們只要求出 \(w_n^0\sim w_n^m\) 就可以知道剩下的了。而 \(f_0,f_1\) 可以遞迴求。
idft(化回來)
不會。但是**和dft是基本一樣的。
首先是直觀但是常數大的遞迴版:
void fft(mle *c,int len,bool sys),z=(mle);
//part 3
rep(i,0,wal-1)
}
part:
把係數分為兩個部分,然後依次遞迴。
計算。其中 \(w_n^0=1,w_n^k=w_n^*ori\)。
其中dft時 \(ori=\cos+\sin\frac}\),idft時 \(ori=\cos-\sin\frac}\)。以及 \(wal\) 就是 \(\frac\)。
做前文的事情。運用 \(w_n^k=w_n^*ori\)。
注意,dft的時候把原本裝係數的陣列變成了現在裝 \(f(w)\) 的陣列。
於是你就輕鬆有了 66分。毫無疑問,常數太大了!
對於第 \(x\) 個係數 \(a_x\) ,它的路徑是怎麼樣的?
0 1 2 3 4 5 6 7
0 2 4 6,1 3 5 7
0 4,2 6,1 5,3 7
0,4,2,6,1,5,3,7
你會發現,
如果把0,4,2,6,1,5,3,7
,
每乙個數都轉為二進位制000, 100, 010, 110, 001, 101, 011, 111
,
再每乙個二進位制反過來000, 001, 010, 011, 100, 101, 110, 111
,
最後化為十進位制0,1,2,3,4,5,6,7
。哦豁!
所以可以快速求得遞迴的底層是怎麼樣的,然後我們模擬遞迴,列舉長度(1,2,4,8……),然後把一段長度的合併。
但是又怎麼樣求二進位制反轉呢?
\(\color\huge 待填!\)
順便提一句小優化,因為複數乘法常數大,所以一般在 \(f=f_0\pm f_1\)是這樣寫:
rep(i,0,wal-1)
最後**:
也是待填
fft**
#include#define rep(i,x,y) for(int i=x;i<=y;++i)
#define lod double
using namespace std;
const int n7=3012345;
const lod pie=acos(-1);
int n,m,rv[n7];
struct mlea[n7],b[n7];
mle operator + (mle p,mle q);}
mle operator - (mle p,mle q);}
mle operator * (mle p,mle q);}
int rd()
void fft(mle *c,bool sys)
fft(a,1),fft(b,1);
rep(i,0,n)a[i]=a[i]*b[i];
fft(a,0);
rep(i,0,m)printf("%d ",(int)(a[i].x/n+0.5));
return 0;
}
ntt**
#include#define rep(i,x,y) for(int i=x;i<=y;++i)
#define lon long long
using namespace std;
const int n7=3012345;const lon mo=998244353;
int n,m,rv[n7];lon a[n7],b[n7];
int rd()
lon dpow(lon p,lon q)
return tot;
}void ntt(lon *c,bool sys)
ntt(a,1),ntt(b,1);
rep(i,0,n)a[i]=a[i]*b[i]%mo;
ntt(a,0);
lon inv=dpow(n,mo-2);
rep(i,0,n)a[i]=a[i]*inv%mo;
rep(i,0,m)printf("%lld ",a[i]);
return 0;
}
演算法筆記 FFT NTT
本文不做證明,詳細證明請看如上資料。fft在演算法競賽中主要用來加速多項式的乘法 普通是多項式乘法時間複雜度的是o n2 而用fft求多項式的乘法可以使時間複雜度達到o nlogn fft求多項式的乘法步驟主要如下圖 其中求值是將係數表達轉換成點值表達,帶入的自變數是wn 1的複數解,稱為dft 插...
FFT NTT數學解釋
fft和ntt真是噩夢呢 既然被fft和ntt坑夠了,坑一下其他的人也未嘗不可呢 設有乙個數a,使得an 1,其中n為滿足an 1的最小正整數 滿足條件的a有哪些呢?更寬泛地說,只要在乙個集合中定義了加法和乘法,而且二者滿足 這些基本上是小學學的吧,除了最後一點之外,其他都是廢話 那麼裡面滿足an ...
FFT NTT中檔題總結
被deepinc 怕了,把一些題放到這裡來 其實這道題放到中檔題也不太合適,個人感覺真的很難,機房裡好像都是頹的題解 因為期望的可加性,把每個點的貢獻單獨處理,即求期望深度 考慮 y 對 x 的貢獻 當且僅當 x y 的路徑上第乙個點就選 y y 才能成為 x 的祖先 所以 y 對 x 的貢獻就是 ...