FFT NTT 學習筆記

2022-05-27 10:57:12 字數 4663 閱讀 3322

顯然因為我不會數學,所以這篇文章會非常「 感性 」。

將兩個多項式乘起來,即求 \(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 的貢獻就是 ...