1. 程式人生 > >[拉格朗日反演][FFT][NTT][多項式大全]詳解

[拉格朗日反演][FFT][NTT][多項式大全]詳解

limit 完成 快速傅裏葉變換 而已 lin 循環 http 思想 !=

1、多項式的兩種表示法

1.系數表示法

我們最常用的多項式表示法就是系數表示法,一個次數界為\(n\)的多項式\(S(x)\)可以用一個向量\(s=(s_0,s_1,s_2,\cdots,s_n-1)\)系數表示如下:\[S(x)=\sum_{k=0}^{n-1}s_kx^k\]

系數表示法很適合做加法,可以在\(O(n)\)的時間復雜度內完成,表達式為:\[S(x)=A(x)+B(x)=\sum_{k=0}^{n-1}(a_k+b_k)x^k\]

當中\[s_k=a_k+b_k\]

但是,系數表示法不適合做乘法,時間復雜度為\(O(n^2)\),表達式為:\[S(x)=A(x)B(x)=\sum_{k=0}^{n-1}\left(\sum_{j=0}^{n-1}a_j b_{k-j}\right)x^k\]

當中\[s_k=\sum_{j=0}^k a_j b_{k-j}\]

這就是卷積的一般形式,記\(s=a\otimes b\),我們要想辦法加速這個過程。

2.點值表示法

顧名思義,點值就是多項式在一個點處的值。多項式\(A(x)\)點值表達是一個集合:\[\{(x_0,y_0),(x_1,y_1),(x_2,y_2),\cdots,(x_{n-1},y_{n-1})\}\]

使得對於\(k=0,1,2,\cdots,n-1\)\(x_k\)兩兩不相同且\(y_k=A(x_k)\)

\(n\)個點可以確定唯一一個\(n\)次多項式。

點值表達有很多優良的性質,加法和乘法都可以在\(O(n)\)

的時間復雜度內完成。

現有\(A(x)\)的點值表達\[\{(x_0,y_0),(x_1,y_1),(x_2,y_2),\cdots,(x_{n-1},y_{n-1})\}\]\(B(x)\)的點值表達\[\{(x_0,y‘_0),(x_1,y‘_1),(x_2,y‘_2),\cdots,(x_{n-1},y‘_{n-1})\}\]

\(C(x)=A(x)+B(x)\)的點值表達為:\[\{(x_0,y_0+y‘_0),(x_1,y_1+y‘_1),(x_2,y_2+y‘_2),\cdots,(x_{n-1},y_{n-1}+y‘_{n-1})\}\]

\(C(x)=A(x)B(x)\)的點值表達為:\[\{(x_0,y_0 y‘_0),(x_1,y_1 y‘_1),(x_2,y_2 y‘_2),\cdots,(x_{n-1},y_{n-1} y‘_{n-1})\}\]

可見,點值表示可以幫助我們更快地進行卷積,可是如何在系數表示法和點值表示法之間相互轉化呢?

2、復數

\(x\)為實數時,無法很好地對轉換方法進行優化。為了優化計算\(x^n\)所浪費的時間,我們需要\(x\)有循環的性質。但點值表示法需要\(n\)個兩兩不同的值,而在實數域中只有\(1\)\(-1\),因此,我們需要復數的幫助。

1.復數、復平面的定義

我們把形如\(a+bi\)的數稱為復數\(z\),其中\(a\)實部(Real),記為\(\Re z\)\(b\)虛部(Imaginary),記為\(\Im z\)

每一點都對應唯一復數的平面叫復平面,相當於一個把\(\Re z\)作為橫坐標,把\(\Im z\)作為縱坐標的笛卡爾坐標系。如圖:
技術分享圖片

模長:復平面上原點到復數\(z\)的距離,記為\(|z|\)。根據勾股定理,\(|z|=|a+bi|=\sqrt{a^2+b^2}\)

輻角:復平面上\(x\)軸與復數\(z\)所對應向量之間的夾角,在\((-\frac{\pi}{2},\frac{\pi}{2})\)之間的記為輻角主值\(\arg z\)

2.歐拉公式

大名鼎鼎的歐拉公式:\[e^{i t}=\cos t+i\sin t\]

根據三角函數在單位圓上的幾何意義,公式是容易理解的。

幾何意義:
技術分享圖片
當中\(\theta\)為角度,\(t\)為弧長。

則根據歐拉公式,可將一個復數表示為一個二元組\((a,\theta)\),即模長和輻角(相當於復平面上極坐標系的表示方法)。值為:\(a(\cos\theta+i\sin\theta)\)

特殊情況:歐拉恒等式\(e^{i\pi}+1=0\)

3.復數的運算

(1)復數加法

運算規則:實部、虛部分別相加\[(a+bi)+(c+di)=a+c+bi+di=(a+c)+(b+d)i\]

幾何意義:如圖
技術分享圖片

結果相當於兩個向量所構成的平行四邊形的對角線。如果把一個復數所對應的向量視為一個移動的變換,那麽向量加法就是連續運用這兩個變換相當於的新變換。

(2)復數乘法

運算規則:展開\[(a+bi)(c+di)=ac+adi+bci+bdi^2=(ac-bd)+(ad+bc)i\]

幾何意義:如圖
技術分享圖片
如圖,\(\arg a+\arg b=\arg a\times b\),\(|a|\times|b|=|a\times b|\)

總結就是:模長相乘,輻角相加

因此,如果模長為\(1\),那麽它的\(n\)次方一定還在單位圓上。

證明:

根據歐拉公式,已知\(x=(a_1,\theta_1)=a_1(\cos\theta_1+i\sin\theta_1),y=(a_2,\theta_2)=a_2(\cos\theta_2+i\sin\theta_2)\)

\[\begin{align*} x\times y&=a_1 a_2(\cos\theta_1+i\sin\theta_1)(\cos\theta_2+i\sin\theta_2)\&=a_1 a_2\left[(\cos\theta_1\cos\theta_2-\sin\theta_1\sin\theta_2)+i(\cos\theta_1\sin\theta_2+\sin\theta_1\cos\theta_2)\right]\&=a_1 a_2\left[\left(\frac{\cos(\theta_1+\theta_2)+\cos(\theta_1-\theta_2)}{2}+\frac{\cos(\theta_1+\theta_2)-\cos(\theta_1-\theta_2)}{2}\right)\right.\&\left.+i\left(\frac{\sin(\theta_1+\theta_2)-\sin(\theta_1-\theta_2)}{2}+\frac{\sin(\theta_1+\theta_2)+\sin(\theta_1-\theta_2)}{2}\right)\right]\tag{積化和差公式}\&=a_1 a_2\left[\cos(\theta_1+\theta_2)+i\sin(\theta_1+\theta_2)\right] \end{align*}\]

\(\therefore |x\times y|=|x|\times |y|,\arg(x\times y)=\arg x+\arg y\)

證畢。

4.單位復數根

(1)基本性質

單位復數根是方程\(\omega^n=1\)的解,第\(k\)個解記為\(\omega_n^k\)(這裏的\(k\)事實上是乘方的含義)

\(n=16\)的解在復平面上的位置如下:
技術分享圖片
可以看到,\(n\)個解把單位圓分成了\(n\)等弧,交點即為根。而且,\(\omega_n^k\)實際上是\(\omega_n\)\(n\)次方,模長仍為\(1\),輻角翻倍!

為什麽呢?

\(\because |x^n|=|x|^n,\arg x^n=n\arg x\)

\(\therefore |\omega|^n=|\omega^n|,\arg\omega^n=n\arg\omega\)

\(\therefore |\omega|^n=1(|\omega|\in\mathbb{R}^+),\arg\omega=\frac{360^\circ}{n}\)

\(\therefore |\omega|=1,\arg\omega=\frac{360^\circ}{n}\)

這就很明顯了。

所以,\(\omega_n^k\)事實上表示的是\(\omega_n\)\(k\)次冪。為什麽選擇單位復數根呢?因為它有循環的優良性質,即\(\omega_n^n=1\)。由於其他的都可以由\(\omega_n^1\)得到,因此稱為主\(n\)次單位根,又記為\(\omega_n\)

根據單位復數根的平分圓的意義和歐拉公式,\(\omega_n^k=e^\frac{2\pi i k}{n}=\cos\frac{2\pi k}{n}+i\sin\frac{2\pi k}{n}\)

(2)計算引理

顯然,由於單位復數根循環(\(\omega_n^{zn}=e^{2\pi iz}=\left[\left(e^{\pi i}\right)^2\right]^z=1^z=1\)),有變換恒等式\[\omega_n^k=\omega_n^{k+wn}(w\in\mathbb{Z})\]

每一份再分成\(k\)份,編號也變成\(k\)倍,位置自然不變(\(\omega_{d n}^{d k}=e^\frac{2\pi i d k}{dn}=e^\frac{2\pi i k}{n}=\omega_n^k\)),所以有消去引理\[\omega_{d n}^{d k}=\omega_n^k\]

由於過了\(n/2\)就會繞過半圈(\(\omega_n^{n/2}=e^\frac{\pi i n}{n}=e^{\pi i}=-1\)),所以有折半引理\[\omega_n^k=-\omega_n^{k\pm n/2}\]

對單位復數根求和,根據幾何級數(等差數列求和公式),可得(\(\sum\limits_{k=0}^{n-1}(\omega_n^k)^j=\frac{(\omega_n^n)^j-1}{\omega_n^1-1}=0\)),即有求和引理(要註意公式的使用條件):\[\sum_{k=0}^{n-1}(\omega_n^k)^j=0,\omega_n^k\ne 1\]

3、DFT&FFT

1.DFT

DFT就是求多項式\(A(x)\)在點\((\omega_n^0,\omega_n^1,\omega_n^2,\cdots,\omega_n^{n-1})\)處取值的過程。即:\[y_k=A(\omega_n^k)=\sum_{j=0}^{n-1}a_j\omega_n^{kj}\]

結果\(y=(y_0,y_1,y_2,\cdots,y_{n-1})\)就是\(a\)離散傅裏葉變換(DFT),記為\(y=DFT_n(a)\)

2.FFT

(1)遞歸

DFT的\(O(n^2)\)算法太慢了,FFT使用分治策略優化速度到\(O(n\log n)\)

考慮奇偶分治。

現在,我們假設\(n=2^t\),設原系數\(a=(a_0,a_1,a_2,\cdots,a_{n-1})\),分治為偶數部分\(a_1=(a_0,a_2,a_4,\cdots,a_{n-2})\),奇數部分\(a_2=(a_1,a_3,a_5,\cdots,a_{n-1})\),已經遞歸求出\(y_1=DFT_{n/2}(a_1)\)\(y_2=DFT_{n/2}(a_2)\),現在我們要合並\(y_1,y_2\),得到\(y=DFT_n(a)\)(蝴蝶操作)。

對於\(n=1\)的邊界情況,結果是顯然的:因為\(k=0\),故\(\omega_1^0=1\),即結果等於原系數。

對於\(n>1\),現在我們枚舉\(k\in[1,n]\)要合並出\(y_k\)
\[\begin{align*} y_k&=A(\omega_n^k)\&=\sum_{j=0}^{n-1}a_j\omega_n^{kj}\&=\sum_{j=0}^{n/2-1}a_{2 j}\omega_n^{2 k j}+\sum_{j=0}^{n/2-1}a_{2 j+1}\omega_n^{2 k j+k}\&=\sum_{j=0}^{n/2-1}a_{2 j}\omega_n^{2 k j}+\omega_n^k\sum_{j=0}^{n/2-1}a_{2 j+1}\omega_n^{2 k j}\&=\sum_{j=0}^{n/2-1}a_{1_j}\omega_{n/2}^{k j}+\omega_n^k\sum_{j=0}^{n/2-1}a_{2_j}\omega_{n/2}^{k j}\tag{消去引理} \end{align*}\]

  • 對於\(k<n/2\)
    \[\begin{align*} y_k&=y_{1_k}+\omega_n^k y_{2_k} \end{align*}\]

  • 對於\(k\geq n/2\)
    \[\begin{align*} y_k&=\sum_{j=0}^{n/2-1}a_{1_j}\omega_{n/2}^{(k-n/2)j}+\omega_n^k\sum_{j=0}^{n/2-1}a_{2_j}\omega_{n/2}^{(k-n/2)j}\tag{變換恒等式}\&=y_{1_{k-n/2}}+\omega_n^k y_{2_{k-n/2}}\&=y_{1_{k-n/2}}-\omega_n^{k-n/2}y_{2_{k-n/2}}\tag{折半引理} \end{align*}\]

我們用\(k+n/2\)替代\(k\),就得到\(y_{k+n/2}=y_{1_k}-\omega_n^k y_{2_k}\)

結合在一起就得到\(\begin{cases}y_k&=y_{1_k}+\omega_n^k y_{2_k}\\y_{k+n/2}&=y_{1_k}-\omega_n^k y_{2_k}\end{cases}\)
這樣我們就可以把兩個\(n/2\)長的序列合並為一個\(n\)長的序列了。

以下圖的遞歸序,就可以在\(O(n\log n)\)的時間復雜度內完成求解了。
技術分享圖片

(2)叠代

遞歸方法消耗內存、時間過大,無法承受。我們每次把下標分為奇數部分和偶數部分,是否有辦法直接求出最後的遞歸運算順序,以避免遞歸呢?

這樣想:
第一次奇偶劃分,我們按照二進制的倒數第一位排序
第二次奇偶劃分,我們按照二進制的倒數第二位排序
\(n\)次奇偶劃分,我們按照二進制的倒數第\(n\)位排序
依此類推。

因此,結果順序就是原序列按照二進制位翻轉的大小排序的結果。只要依次交換\(a_k,a_{rev(k)}\),求出序列,就可以用叠代方法相鄰歸並實現快速傅裏葉變換。

或者,我們也可以用更加代數的方法來發現這個結論。
已知現在位置為\(k=(b_1 b_2 b_3 \cdots b_n)_2\),按照奇偶重排:

  • \(b_n=0\),則位置變為\(\frac{k}{2}=(0 b_1 b_2 \cdots b_{n-1})_2=(b_n b_1 b_2 \cdots b_{n-1})_2\),即為把最後一位提到第一位。
  • \(b_n=1\),則位置變為\(2^{n-1}-1+\frac{k+1}{2}=\frac{2^n+k-1}{2}=\frac{(1 b_1 b_2 \cdots b_{n-1} 0)_2}{2}=(b_n b_1 b_2 \cdots b_{n-1})_2\),同樣是把最後一位提到第一位。

則反復\(n\)次之後,就相當於二進制反轉了。

\(n=8\)時,求出二進制:
|0|1|2|3|4|5|6|7|
|:|:|:|:|:|:|:|:|
|\(000_2\)|\(001_2\)|\(010_2\)|\(011_2\)|\(100_2\)|\(101_2\)|\(110_2\)|\(111_2\)|
翻轉:
|0|1|2|3|4|5|6|7|
|:|:|:|:|:|:|:|:|
|\(000_2\)|\(100_2\)|\(010_2\)|\(110_2\)|\(001_2\)|\(101_2\)|\(011_2\)|\(111_2\)|
按翻轉後的值排序:
|0|4|2|6|1|5|3|7|
|:|:|:|:|:|:|:|:|
|\(000_2\)|\(001_2\)|\(010_2\)|\(011_2\)|\(100_2\)|\(101_2\)|\(110_2\)|\(111_2\)|
這樣就可以把奇偶合並轉化為左右歸並,叠代實現了。

5、IDFT&IFFT

何把點值表達變回系數表達呢?如果把求值寫成矩陣形式,就是:
\[\begin{bmatrix} \omega_n^0 & \omega_n^0 & \omega_n^0 & \omega_n^0 & \cdots & \omega_n^0 \\omega_n^0 & \omega_n^1 & \omega_n^2 & \omega_n^3 & \cdots & \omega_n^{n-1} \\omega_n^0 & \omega_n^2 & \omega_n^4 & \omega_n^6 & \cdots & \omega_n^{2(n-1)} \\omega_n^0 & \omega_n^3 & \omega_n^6 & \omega_n^9 & \cdots & \omega_n^{3(n-1)} \\vdots & \vdots & \vdots & \vdots & \ddots & \vdots \\omega_n^0 & \omega_n^{n-1} & \omega_n^{2(n-1)} & \omega_n^{3(n-1)} & \cdots & \omega_n^{(n-1)^2} \end{bmatrix} \begin{bmatrix} a_0 \\ a_1 \\ a_2 \\ a_3 \\ \vdots \\ a_{n-1} \end{bmatrix}= \begin{bmatrix} y_0 \\ y_1 \\ y_2 \\ y_3 \\ \vdots \\ y_{n-1} \end{bmatrix}\]

如果我們要把\(y\)變成\(a\),就需要求出第一個矩陣的逆,即:
\[\begin{bmatrix} a_0 \\ a_1 \\ a_2 \\ a_3 \\ \vdots \\ a_{n-1} \end{bmatrix}= \begin{bmatrix} \omega_n^0 & \omega_n^0 & \omega_n^0 & \omega_n^0 & \cdots & \omega_n^0 \\omega_n^0 & \omega_n^1 & \omega_n^2 & \omega_n^3 & \cdots & \omega_n^{n-1} \\omega_n^0 & \omega_n^2 & \omega_n^4 & \omega_n^6 & \cdots & \omega_n^{2(n-1)} \\omega_n^0 & \omega_n^3 & \omega_n^6 & \omega_n^9 & \cdots & \omega_n^{3(n-1)} \\vdots & \vdots & \vdots & \vdots & \ddots & \vdots \\omega_n^0 & \omega_n^{n-1} & \omega_n^{2(n-1)} & \omega_n^{3(n-1)} & \cdots & \omega_n^{(n-1)^2} \end{bmatrix}^{-1} \begin{bmatrix} y_0 \\ y_1 \\ y_2 \\ y_3 \\ \vdots \\ y_{n-1} \end{bmatrix}\]

這個範德蒙德矩陣極為特殊,它的逆矩陣是:
\[\begin{bmatrix} a_0 \\ a_1 \\ a_2 \\ a_3 \\ \vdots \\ a_{n-1} \end{bmatrix}=\frac{1}{n} \begin{bmatrix} \omega_n^0 & \omega_n^0 & \omega_n^0 & \omega_n^0 & \cdots & \omega_n^0 \\omega_n^0 & \omega_n^{-1} & \omega_n^{-2} & \omega_n^{-3} & \cdots & \omega_n^{-(n-1)} \\omega_n^0 & \omega_n^{-2} & \omega_n^{-4} & \omega_n^{-6} & \cdots & \omega_n^{-2(n-1)} \\omega_n^0 & \omega_n^{-3} & \omega_n^{-6} & \omega_n^{-9} & \cdots & \omega_n^{-3(n-1)} \\vdots & \vdots & \vdots & \vdots & \ddots & \vdots \\omega_n^0 & \omega_n^{-(n-1)} & \omega_n^{-2(n-1)} & \omega_n^{-3(n-1)} & \cdots & \omega_n^{-(n-1)^2} \end{bmatrix} \begin{bmatrix} y_0 \\ y_1 \\ y_2 \\ y_3 \\ \vdots \\ y_{n-1} \end{bmatrix}\]

只是把每項取倒數,並將結果除以\(n\)即可。

證明

記原矩陣為\(A_{n n}\),我們給出的矩陣為\(B_{n n}\)

\(\therefore A_{i j}=\omega_n^{i j},B_{i j}=\frac{1}{n}\omega_n^{-i j}(0\le i,j\le n-1)\)

\[\begin{align*} AB_{i j}&=\sum_{k=0}^{n-1} A_{i k}B_{k j}\&=\frac{1}{n}\sum_{k=0}^{n-1} \omega_n^{i k}\omega_n^{-k j}\&=\frac{1}{n}\sum_{k=0}^{n-1} \omega_n^{i k-k j}\&=\frac{1}{n}\sum_{k=0}^{n-1} (\omega_n^{i-j})^k \end{align*}\]

  • \(i=j\)時:

\[\begin{align*} AB_{i j}&=\frac{1}{n}\sum_{k=0}^{n-1}1^k\&=\frac{1}{n}\times n\&=1 \end{align*}\]

  • \(i\ne j\)時:

\[\begin{align*} AB_{i j}&=\frac{1}{n}\sum_{k=0}^{n-1}(\omega_n^{i-j})^k\&=0\tag{求和引理} \end{align*}\]

綜上所述,\(AB=I_n\),即\(B=A^{-1}\)

以上,離散傅裏葉逆變換(IDFT)的表達式為:\[a_k=\frac{1}{n}\sum_{j=0}^{n-1}y_k\omega_n^{-k j}\]
\(a=IDFT_n(y)\)

同理,可以用相同的方法把IDFT加速到\(O(n\log n)\),稱為IFFT。

5、NTT

1.定義

有時候我們會想要模素數\(p\)意義下的多項式乘法。此時,由次數界為\(n\)的多項式\(A(x),B(x)\)的系數表達\(a,b\)\(S(x)=A(x)B(x)\)的系數表達\(s\)的公式為:\[s_k=\sum_{j=0}^k a_j b_{k-j}\mod p\]

FFT無能為力,我們需要一種新的DFT,以數論的辦法進行,這就是快速數論變換(NTT)

2.原根

(1)定義

我們需要一種有數論循環性質的新數,原根恰好滿足我們的要求。

\(m\)為正整數,\(a\)為整數,若\(a\)\(m\)的階等於\(\varphi(m)\),則稱\(a\)為模\(m\)的一個原根。

假設\(g\)是素數\(p\)的原根,有\(1<g<p\),且對於\(k=0,1,2,\cdots,p-1\),有\(g^k\mod p\)的結果兩兩不同,且\(g^{p-1}\equiv 1 \pmod p\)

可以發現,原根同樣有循環的性質。因此,我們類比\(\omega_n^k\)的定義,把原來的\(\omega_n^k=e^\frac{2\pi ik}{n}\)替換為\(g^\frac{k(p-1)}{n}\)

(2)性質

我們來證明一些類似單位復數根的性質。

變換恒等式
因為:\[g^{p-1}\equiv 1\]

所以:\[g^\frac{(k+n)(p-1)}{n}\equiv g^\frac{k(p-1)}{n}g^{p-1}\equiv g^\frac{k(p-1)}{n}\]


消去引理
顯然有:\[g^\frac{d k(p-1)}{d n}\equiv g^\frac{k(p-1)}{n}\]


折半引理
因為:\[g^\frac{(k+\frac{n}{2})(p-1)}{n}\equiv g^\frac{k(p-1)}{n}g^\frac{p-1}{2}\]

所以若要使:\[g^\frac{k(p-1)}{n}+g^\frac{(k+\frac{n}{2})(p-1)}{n}\equiv 0\pmod p\]

成立,即:\[g^\frac{k(p-1)}{2}\left(1+g^\frac{p-1}{2}\right)\equiv 0\]

只需證:\[g^\frac{p-1}{2}\equiv p-1\]

由於:\[g^{k}\equiv 0,1,2,\cdots,p-1\]

那麽我們可以設:\[g^\frac{p-1}{2}\equiv x,x=0,1,2,\cdots,p-1\]

因為:\[\left(g^\frac{p-1}{2}\right)^2\equiv g^{p-1}\equiv 1\]

所以:\[x^2-1\equiv 0\]

即:\[(x+1)(x-1)\equiv 0\]

又因為\(p\)為素數,所以有:\[x+1\equiv 0\;或\;x-1\equiv 0\]

所以:\[x=1,p-1\]

又因為:\[g^{p-1}\equiv 1,g^k\mod p兩兩不同\]

所以:\[x=p-1\]

即:\[g^\frac{p-1}{2}\equiv p-1\]

得證:\[g^\frac{k(p-1)}{n}+g^\frac{(k+\frac{n}{2})(p-1)}{n}\equiv 0\]


求和引理
\[\sum\limits_{k=0}^{n-1}g^\frac{k j(p-1)}{n}\equiv\sum\limits_{k=0}^{n-1}\left(g^\frac{j(p-1)}{n}\right)^k\equiv\frac{g^\frac{n j(p-1)}{n}-1}{g^\frac{j(p-1)}{n}-1}\equiv\frac{\left(g^{p-1}\right)^j-1}{g^\frac{j(p-1)}{n}-1}\equiv 0\]

這樣,我們就證明了原根由於復數單位根相同的性質。

3.公式

我們用原根替換復數單位根,得到:\[y_k=A(g^\frac{k(p-1)}{n})=\sum_{j=0}^{n-1}a_k g^\frac{k j(p-1)}{n}\]

\(y=NTT_n(a)\)。逆變換:\[a_k=\frac{1}{n}\sum_{j=0}^{n-1}y_k g^\frac{-k j(p-1)}{n}\]

\(a=INTT_n(y)\)

6、其他擴展

1.任意模數FFT

有的時候我們需要卷積後模上一個數,這個數不是NTT模數,甚至可能不是個質數。那我們該怎麽做呢?

這裏使用拆系數FFT,本質是以時間換精度。

現在給定次數界為\(m\)的兩個多項式\(A(x),B(x)\),要求\(A(x)B(x) \mod P\)

首先,令\(M=\lfloor\sqrt{P}\rfloor\),再對於每個\(a_i\)\(b_i\),把其變為\(k_i M+r_i(r_i<M)\)的形式。這樣,\(k_i\)\(r_i\)就都小於等於\(M\)了。

那麽卷積就可以寫成:\[c_i=\sum_{k=0}^i a_k b_{i-k}=\sum_{k=0}^i(k_{a_i}M+r_{a_i})(k_{b_{i-k}}M+r_{b_{i-k}})=M^2\sum_{k=0}^i k_{a_i}k_{b_{i-k}}+M\sum_{k=0}^i (k_{a_i}r_{b_{i-k}}+r_{a_i}k_{b_{i-k}})+\sum_{k=0}^i r_{a_i}r_{b_{i-k}}\]

那麽我們看到,\(c_i\)可以由\(k\)\(r\)合並得到。那麽我們對\(k_a,k_b,r_a,r_b\)分別做FFT,再對應算出\(x_1=k_a k_b,x_2=k_a r_b+r_a k_b,x_3=r_a r_b\),對它們再分別做IFFT,就可以由\(c=M^2 x_1+M x_2+x_3\)得到答案了。

這麽做的好處究竟在哪裏呢?事實上,計算後的最大值由\(m P\)變為了\(m \lfloor\sqrt{P}\rfloor\),避免了溢出。

時間復雜度:\(O(n\log n)\)

2.多項式求逆

現在我們有一個次數界為\(n\)的多項式\(A(x)\),要求\(B(x)\)滿足\(A(x)B(x)\equiv 1\pmod{x^n}\)

我們考慮倍增實現。

  • \(n=1\)時,直接求逆元求得\(B(x)\equiv A(x)^{p-2}\)
  • \(n>1\)時,已有\(A(x)G(x)\equiv 1\pmod{x^\frac{n}{2}}\)

因為:\[A(x)B(x)\equiv 1\pmod{x^n}\]

又因為除了\(0\)次項之外到\(n-1\)次都為\(0\),因此到\(\frac{n}{2}-1\)次項也為零:\[A(x)B(x)\equiv 1\pmod{x^\frac{n}{2}}\]

\[A(x)G(x)\equiv 1\pmod{x^\frac{n}{2}}\]

兩式相減:\[A(x)[B(x)-G(x)]\equiv 0\pmod{x^\frac{n}{2}}\]

因為:\[A(x)\ne 0\]

所以:\[B(x)-G(x)\equiv 0\pmod{x^\frac{n}{2}}\]

既然\(0\)\(\frac{n}{2}-1\)次項都為零,那麽平方之後\(0\)\(n-1\)次項也為零:\[B(x)^2-2 B(x)G(x)+G(x)^2\equiv 0\pmod{x^n}\]

\[A(x)B(x)\equiv 1\pmod{x^n}\]

兩邊同時乘上\(A(x)\),得:\[B(x)-2 G(x)+A(x)G(x)^2\equiv 0\pmod{x^n}\]

移項,得:\[B(x)\equiv 2 G(x)-A(x)G(x)^2\pmod{x^n}\]

這樣就可以了。

時間復雜度:\(O(n\log n)\)

3.多項式開根

前置:多項式求逆。

現在我們有一個次數界為\(n\)的多項式\(A(x)\),要求\(B(x)\)滿足\(B(x)^2\equiv A(x)\pmod{x^n}\)

還是倍增。

  • \(n=1\)時,\(B(x)\)等於\(A(x)\)在模意義下的開根。
  • \(n>1\)時,已有\(G(x)^2\equiv A(x)\pmod{x^\frac{n}{2}}\)

因為:\[G(x)^2\equiv A(x)\pmod{x^\frac{n}{2}}\]

移項,得:\[G(x)^2-A(x)\equiv 0\pmod{x^\frac{n}{2}}\]

兩邊平方,同理可得:\[G(x)^4-2 G(x)^2 A(x)+A(x)^2\equiv 0\pmod{x^n}\]

所以:\[[G(x)^2+A(x)]^2-4 G(x)^2 A(x)\equiv 0\pmod{x^n}\]

即:\[[G(x)^2+A(x)]^2\equiv 4 G(x)^2 A(x)\pmod{x^n}\]

除過去:\[\frac{[G(x)^2+A(x)]^2}{4 G(x)^2}\equiv A(x)\pmod{x^n}\]

得到:\[A(x)\equiv\left(\frac{G(x)^2+A(x)}{2 G(x)}\right)^2\equiv B(x)^2\pmod{x^n}\]

即:\[B(x)\equiv\frac{G(x)^2+A(x)}{2 G(x)}\equiv\frac{A(x)}{2 G(x)}+\frac{G(x)}{2}\pmod{x^n}\]

這就可以了。

時間復雜度:\(O(n\log n)\)

4.多項式求導

根據導數的可加性和冪函數求導法則\(\frac{\mathbb{d}(cx^k)}{\mathbb{d}x}=c k x^{k-1}\),有多項式的導數為:\[\frac{\mathbb{d}(\sum\limits_{k=0}^{n-1}a_k x^k)}{\mathbb{d} x}=\sum_{k=0}^{n-1}\frac{\mathbb{d}(a_k x^k)}{\mathbb{d} x}=\sum_{k=1}^{n-1}k a_k x^{k-1}=\sum_{k=0}^{n-2}(k+1)a_{k+1}x^k\]

時間復雜度:\(O(n)\)

5.多項式積分

根據積分的可加性和冪函數的不定積分\(\displaystyle\int c x^k\mathbb{d}x=\frac{c}{k}x^{k+1}\),有多項式的不定積分為:\[\int\sum_{k=0}^{n-1}a_k x^k \mathbb{d}x=\sum_{k=0}^{n-1}\int a_k x^k\mathbb{d}x=\sum_{k=0}^{n-1}\frac{a_k}{k}x^{k+1}=\sum_{k=1}^n\frac{a_{k-1}}{k-1}x^k\]

時間復雜度:\(O(n)\)

6.多項式求ln

前置:多項式求導&積分&求逆

現在已知多項式\(A(x)\),要求\(B(x)=\ln A(x)\)。我們兩邊微分,得到:\[B‘(x)=\frac{\mathbb{d}(\ln A(x))}{\mathbb{d} A(x)}\frac{\mathbb{d} A(x)}{\mathbb{d} x}\]
\[B‘(x)=\frac{A‘(x)}{A(x)}\]

再兩邊積分,就得到:\[B(x)=\int\frac{A‘(x)}{A(x)}\mathbb{d}x\]

因此,我們直接多項式求導+求逆+積分解決。

時間復雜度:\(O(n\log n)\)

7.多項式求exp

前置:多項式求ln

現在,我們已知多項式\(A=A(x)\)(這樣寫是因為在這裏把\(A\)視為是與\(x\)無關的),求\(F(x)=\exp(A)=e^{A}\)。只要我們設\(G(x)=\ln x-A\),就得到:\[G(F(x))=\ln F(x)-A=0\]

我們考慮用牛頓叠代法倍增解這個方程。

對於牛頓叠代法的初始解,即結果的常數項,我們並不知道具體值。但是如果不對的話,也只是缺少了一個常數罷了,那我們不妨設\(F(x)=1\)

倍增:現在設我們已經求出了\(F(x)\)的前\(n\)\(F_0(x)\),即:\[F_0(x)\equiv F(x)\pmod{x^n}\]

根據牛頓叠代法,我們求出下一個近似解為:\[F(x)\equiv F_0(x)-\frac{G(F_0(x))}{G‘(F_0(x))}\equiv F_0(x)-\frac{\ln F_0(x)-A}{F_0(x)^{-1}}\equiv F_0(x)(1-\ln F_0(x)+A(x))\]

如此,就可以倍增實現了。

時間復雜度:\(O(n\log n)\)

8.多項式求冪

已知多項式\(A(x)\)和指數\(k\),求\(A(x)^k\)

在冪數很大的時候,為了確定出系數,時間復雜度為\(O(k n \log (nk))\),我們必須進行優化。

我們發現:\[A(x)^k=\left(e^{\ln A(x)}\right)^k=e^{k \ln A(x)}=\exp(k \ln A(x))\]

於是我們可以用多項式求ln+多項式求exp在\(O(n \log n)\)的時間內求出。

7、代碼實現

1.二進制反轉

可以用一種類似dp的思想計算。

邊界:\(0\)的二進制翻轉為\(0\)

遞歸式:對於\(a\),已經算出了\(rev_{\lfloor\frac{a}{2}\rfloor}\)\(a\)就是除去最後一位的二進制翻轉(\(rev_{\lfloor\frac{a}{2}\rfloor}\))向後移動一位再補上第一位。即:

rev[a]=(rev[a>>1]>>1)|((a&1)<<(l-1))

\(l\)為要翻轉的位數。

2.復數類

套公式即可。

struct complex{
    double re,im;
    complex(double re=0,double im=0):re(re),im(im){}
    complex operator+(complex &b)const{return complex(re+b.re,im+b.im);}
    complex operator-(complex &b)const{return complex(re-b.re,im-b.im);}
    complex operator*(complex &b)const{return complex(re*b.re-im*b.im,re*b.im+im*b.re);}
};

3.FFT

  • 1:根據二進制翻轉交換\(a_r\)\(a_{rev(r)}\)
  • 2:枚舉歸並步長\(i\in[1,n)\)\(i\)為二的冪;
    • 2.1: 根據歐拉公式求出\(\omega_i^1=\cos\frac{\pi}{i}+i\sin\frac{\pi}{i}\)
    • 2.2:枚舉歸並位置\(j\),歸並\([j,j+i)\)\([j+i,j+2 i)\),步長為\(2 i\)
      • 2.2.1:枚舉\(x\)的冪數\(k\in[0,i)\)進行蝴蝶操作計算\(y\),根據單位根計算\(\omega_i^k\)
        • 2.2.1.1:\(y_{j+k}=y_{j+k}+\omega_i^k y_{j+k+i}\)
        • 2.2.1.2:\(y_{j+k+i}=y_{j+k}-\omega_i^k y_{j+k+i}\)

註意:由於在C++中值會被更改,因此需要引入臨時變量。

void FFT(complex c[]){
    for(int i=0;i<n;i++)if(i<r[i])swap(c[i],c[r[i]]);
    for(int i=1;i<n;i<<=1){
        complex omn(cos(pi/i),sin(pi/i));
        for(int j=0;j<n;j+=(i<<1)){
            complex om(1,0);
            for(int k=0;k<i;k++,om=om*omn){
                complex x=c[j+k],y=om*c[j+k+i];
                c[j+k]=x+y;
                c[j+k+i]=x-y;
            }
        }
    }
}

4.IFFT

由於公式中只差一個負號而已,因此引入一個參數\(type\),在歐拉公式的地方乘上去,再除以\(n\)就可以了。

void FFT(complex c[],int tp=1){
    for(int i=0;i<n;i++)if(i<r[i])swap(c[i],c[r[i]]);
    for(int i=1;i<n;i<<=1){
        complex omn(cos(pi/i),tp*sin(pi/i));
        for(int j=0;j<n;j+=(i<<1)){
            complex om(1,0);
            for(int k=0;k<i;k++,om=om*omn){
                complex x=c[j+k],y=om*c[j+k+i];
                c[j+k]=x+y;
                c[j+k+i]=x-y;
            }
        }
    }
}
void IFFT(complex c[]){
    FFT(c,-1);
    for(int i=0;i<=m;i++)a[i].re/=n;
}

5.多項式乘法

模板:洛谷3083

註意:

1、由於FFT要求\(n\)\(2\)的冪且結果的次數界較大,所以要把兩個因式的系數補到\(l\)位,\(l\)滿足\(l=2^t\)\(l/2\)大於等於因式的次數界。

2、\(FFT\)雖然在數學上精準,但在C++中誤差巨大,因此虛部不會為\(0\),忽略即可。實部也不為正數,可以加上\(0.1\)再向下取整。

代碼:

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
const double pi=acos(-1);
struct complex{
    double re,im;
    complex(double re=0,double im=0):re(re),im(im){}
    complex operator+(complex &b)const{return complex(re+b.re,im+b.im);}
    complex operator-(complex &b)const{return complex(re-b.re,im-b.im);}
    complex operator*(complex &b)const{return complex(re*b.re-im*b.im,re*b.im+im*b.re);}
}a[2097153],b[2097153];
int n,m,l,r[2097153];
void FFT(complex c[],int tp=1){
    for(int i=0;i<n;i++)if(i<r[i])swap(c[i],c[r[i]]);
    for(int i=1;i<n;i<<=1){
        complex omn(cos(pi/i),tp*sin(pi/i));
        for(int j=0;j<n;j+=(i<<1)){
            complex om(1,0);
            for(int k=0;k<i;k++,om=om*omn){
                complex x=c[j+k],y=om*c[j+k+i];
                c[j+k]=x+y;
                c[j+k+i]=x-y;
            }
        }
    }
}
void IFFT(complex c[]){
    FFT(c,-1);
    for(int i=0;i<=m;i++)a[i].re/=n;
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=0;i<=n;i++)scanf("%lf",&a[i].re);
    for(int i=0;i<=m;i++)scanf("%lf",&b[i].re);
    m+=n;
    for(n=1;n<=m;n<<=1)l++;
    for(int i=0;i<n;i++)r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
    FFT(a);
    FFT(b);
    for(int i=0;i<=n;i++)a[i]=a[i]*b[i];
    IFFT(a);
    for(int i=0;i<=m;i++)printf("%d ",int(a[i].re+0.5));
}

6.(I)FFT+(I)NTT

給出一個\(n\)次多項式和一個\(m\)次多項式的系數表達(\(1\le n,m\le 4000000\)),求乘積。\(type=0\)時,直接計算;\(type=1\)時,結果取模\(998244353\)(原根為\(3\))。

註:為了方便閱讀,代碼效率不高。若要提速,可以把單位根打表。而且,由於\(g^\frac{p-1}{n}\)\(\frac{p-1}{n}\)必須為整數,故僅在第一個比\(n+m\)大的\(2\)的整數次冪是\(p-1\)的約數時才可行,此處\(998244353-1=998244352=119\times2^{23}\)\(2^{23}=8388608>n+m\)

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
const double pi=acos(-1);
const int p=998244353;
typedef long long ll;
struct complex{
    double re,im;
    complex(double re=0,double im=0):re(re),im(im){}
    complex operator+(complex b)const{return complex(re+b.re,im+b.im);}
    complex operator-(complex b)const{return complex(re-b.re,im-b.im);}
    complex operator*(complex b)const{return complex(re*b.re-im*b.im,re*b.im+im*b.re);}
}af[131073],bf[131073];
struct modp{
    int a;
    modp(int a=0):a(a){}
    modp operator+(modp b)const{return (a+b.a)%p;}
    modp operator-(modp b)const{return (a-b.a+p)%p;}
    modp operator*(modp b)const{return ll(a)*b.a%p;}
    modp operator/(modp b)const{return (b^(p-2))*a;}
    modp operator^(int b)const{
        modp ans=1,bs=a;
        while(b){
            if(b&1)ans=ans*bs;
            bs=bs*bs;
            b>>=1;
        }
        return ans;
    }
}an[131073],bn[131073];
const modp g=3;
int n,m,l,r[131073],type;
modp gn[18];
void init(){
    m+=n;
    for(n=1;n<=m;n<<=1)l++;
    for(int i=0;i<n;i++)r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
    for(int i=0;i<=17;i++)gn[i]=g^((p-1)/(1<<i));
}
void FFT(complex c[],int tp=1){
    for(int i=0;i<n;i++)if(i<r[i])swap(c[i],c[r[i]]);
    for(int i=1;i<n;i<<=1){
        complex omn(cos(pi/i),tp*sin(pi/i));
        for(int j=0;j<n;j+=(i<<1)){
            complex om(1,0);
            for(int k=0;k<i;k++,om=om*omn){
                complex x=c[j+k],y=om*c[j+k+i];
                c[j+k]=x+y;
                c[j+k+i]=x-y;
            }
        }
    }
}
void IFFT(complex c[]){
    FFT(c,-1);
    for(int i=0;i<=n;i++)c[i].re/=n;
}
void NTT(modp c[],int tp=1){
    for(int i=0;i<n;i++)if(i<r[i])swap(c[i],c[r[i]]);
    for(int i=1,id=1;i<n;i<<=1,id++){
        modp ggn=gn[id]^(tp==1?1:p-2);
        for(int j=0;j<n;j+=(i<<1)){
            modp gg=1;
            for(int k=0;k<i;k++,gg=gg*ggn){
                modp x=c[j+k],y=gg*c[j+k+i];
                c[j+k]=x+y;
                c[j+k+i]=x-y;
            }
        }
    }
}
void INTT(modp c[]){
    NTT(c,-1);
    for(int i=0;i<=n;i++)c[i]=c[i]/n;
}
int main(){
    scanf("%d%d%d",&n,&m,&type);
    if(type==0){
        for(int i=0;i<=n;i++)scanf("%lf",&af[i].re);
        for(int i=0;i<=m;i++)scanf("%lf",&bf[i].re);
    }else{
        for(int i=0;i<=n;i++)scanf("%d",&an[i].a);
        for(int i=0;i<=m;i++)scanf("%d",&bn[i].a);
    }
    init();
    if(type==0){
        FFT(af);
        FFT(bf);
        for(int i=0;i<=n;i++)af[i]=af[i]*bf[i];
        IFFT(af);
        for(int i=0;i<=m;i++)printf("%d ",int(af[i].re+0.5));
    }else{
        NTT(an);
        NTT(bn);
        for(int i=0;i<=n;i++)an[i]=an[i]*bn[i];
        INTT(an);
        for(int i=0;i<=m;i++)printf("%d ",an[i].a);
    }
    printf("\n");
}

常數只有上面那個的三分之一的NTT(正式考試請務必采用這種寫法):

PS:有一道題上面那個3700ms,這個1000ms

inline ll pow(ll a,int b){
    ll ans=1;
    while(b){
        if(b&1)ans=ans*a%p;
        a=a*a%p;
        b>>=1;
    }
    return ans;
}
inline ll add(ll a,ll b){return a+b>p?a+b-p:a+b;}
inline ll cut(ll a,ll b){return a-b<0?a-b+p:a-b;}
void init(){
    for(n=1;n<=s;n<<=1);
    nn=n;
    gn[0][0]=gn[1][0]=1;
    gn[0][1]=pow(g,(p-1)/(n<<1));
    gn[1][1]=pow(gn[0][1],p-2);
    for(int i=2;i<(n<<1);i++){gn[0][i]=gn[0][i-1]*gn[0][1]%p;gn[1][i]=gn[1][i-1]*gn[1][1]%p;}
    inv[1]=1;
    for(int i=2;i<=(n<<1);i++)inv[i]=inv[p%i]*(p-p/i)%p;
}
void NTT(ll c[],int n,int tp=1){
    for(int i=0;i<n;i++){
        r[i]=(r[i>>1]>>1)|((i&1)*(n>>1));
        if(i<r[i])swap(c[i],c[r[i]]);
    }
    for(int i=1;i<n;i<<=1){
        for(int j=0;j<n;j+=(i<<1)){
            for(int k=0;k<i;k++){
                ll x=c[j+k],y=gn[tp!=1][nn/i*k]*c[j+k+i]%p;
                c[j+k]=add(x,y);
                c[j+k+i]=cut(x,y);
            }
        }
    }
}
void INTT(ll c[],int n){
    NTT(c,n,-1);
    for(int i=0;i<n;i++)c[i]=c[i]*inv[n]%p;
}

7.任意模數FFT

給定\(n,m,P\),再給定次數界為\(n\)的第一個多項式和次數界為\(m\)的第二個多項式,求兩個多項式的卷積模\(P\)

註意:拆系數FFT精度損失極大,需要使用long double保證正確性。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
typedef long long ll;
int x,n,m,l,r[262145],pm,p;
const long double pi=acos(-1);
struct complex{
    long double re,im;
    complex(long double re=0,long double im=0):re(re),im(im){}
    complex operator+(const complex &b)const{return complex(re+b.re,im+b.im);}
    complex operator-(const complex &b)const{return complex(re-b.re,im-b.im);}
    complex operator*(const complex &b)const{return complex(re*b.re-im*b.im,re*b.im+im*b.re);}
}k1[262145],r1[262145],k2[262145],r2[262145],c1[262145],c2[262145],c3[262145];
void FFT(complex c[],int tp=1){
    for(int i=0;i<n;i++)if(i<r[i])swap(c[i],c[r[i]]);
    for(int i=1;i<n;i<<=1){
        complex omn(cos(pi/i),tp*sin(pi/i));
        for(int j=0;j<n;j+=(i<<1)){
            complex om(1,0);
            for(int k=0;k<i;k++,om=om*omn){
                complex x=c[j+k],y=om*c[j+k+i];
                c[j+k]=x+y;
                c[j+k+i]=x-y;
            }
        }
    }
}
void IFFT(complex c[]){
    FFT(c,-1);
    for(int i=0;i<=n;i++)c[i].re/=n;
}
void init(){
    m+=n;
    for(n=1;n<=m;n<<=1)l++;
    for(int i=0;i<n;i++)r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
}
int main(){
    scanf("%d%d%d",&n,&m,&p);
    pm=sqrt(p);
    for(int i=0;i<=n;i++){
        scanf("%d",&x);
        k1[i]=x/pm;
        r1[i]=x%pm;
    }
    for(int i=0;i<=m;i++){
        scanf("%d",&x);
        k2[i]=x/pm;
        r2[i]=x%pm;
    }
    init();
    FFT(k1);
    FFT(r1);
    FFT(k2);
    FFT(r2);
    for(int i=0;i<=n;i++){
        c1[i]=k1[i]*k2[i];
        c2[i]=k1[i]*r2[i]+r1[i]*k2[i];
        c3[i]=r1[i]*r2[i];
    }
    IFFT(c1);
    IFFT(c2);
    IFFT(c3);
    for(int i=0;i<=m;i++){
        ll s1=ll(c1[i].re+0.5)%p*pm%p*pm%p,s2=ll(c2[i].re+0.5)%p*pm%p,s3=ll(c3[i].re+0.5)%p;
        printf("%lld ",((s1+s2)%p+s3)%p);
    }
}

7.多項式的運算

依賴關系:
技術分享圖片
直接按照公式打就好了。

我們先修改NTT:

void NTT(modp c[],int n,int tp=1){
    for(int i=0;i<n;i++){
        r[i]=(r[i>>1]>>1)|((i&1)*(n>>1));
        if(i<r[i])swap(c[i],c[r[i]]);
    }
    for(int i=1,id=1;i<n;i<<=1,id++){
        modp ggn=gn[id]^(tp==1?1:p-2);
        for(int j=0;j<n;j+=(i<<1)){
            modp gg=1;
            for(int k=0;k<i;k++,gg=gg*ggn){
                modp x=c[j+k],y=gg*c[j+k+i];
                c[j+k]=x+y;
                c[j+k+i]=x-y;
            }
        }
    }
}
void INTT(modp c[],int n){
    NTT(c,n,-1);
    for(int i=0;i<n;i++)c[i]=c[i]/n;
}

求逆:

void inverse(modp c[],int n=n){
    static modp t[262145],tma[262145];
    t[0]=c[0]^(p-2);
    for(int k=2;k<=n;k<<=1){
        for(int i=0;i<(k<<1);i++)tma[i]=(i<k?c[i]:0);
        for(int i=(k>>1);i<(k<<1);i++)t[i]=0;
        NTT(tma,k<<1);
        NTT(t,k<<1);
        for(int i=0;i<(k<<1);i++)t[i]=t[i]*2-t[i]*t[i]*tma[i];
        INTT(t,k<<1);
    }
    memcpy(c,t,sizeof(int)*n);
}

開根(\(c[0]=1\)):

void sqrt(modp c[],int n=n){
    static modp t[262145],tma[262145],tmb[262145];
    t[0]=1;
    for(int k=2;k<=n;k<<=1){
        for(int i=0;i<k;i++)tma[i]=t[i]*2;
        inverse(tma,k);
        for(int i=0;i<(k<<1);i++)tmb[i]=(i<k?c[i]:0);
        NTT(tma,k<<1);
        NTT(tmb,k<<1);
        for(int i=0;i<(k<<1);i++){
            modp tmp=tma[i];
            tma[i]=t[i];
            t[i]=tmp*tmb[i];
        }
        INTT(t,k<<1);
        for(int i=0;i<(k<<1);i++)t[i]=(i<k?t[i]+tma[i]/2:0);
    }
    memcpy(c,t,sizeof(int)*n);
}

求導:

void derivative(modp c[],int n=n){for(int i=0;i<n;i++)c[i]=c[i+1]*(i+1);}

積分:

void integrate(modp c[],int n=n){for(int i=n-1;i>=1;i--)c[i]=c[i-1]*inv[i];c[0]=0;}

求ln:

void ln(modp c[],int n=n){
    static modp t[262145];
    for(int i=0;i<(n<<1);i++)t[i]=(i<n?c[i]:0);
    derivative(t,n);
    inverse(c,n);
    NTT(t,n<<1);
    NTT(c,n<<1);
    for(int i=0;i<(n<<1);i++)c[i]=c[i]*t[i];
    INTT(c,n<<1);
    for(int i=n;i<(n<<1);i++)c[i]=0;
    integrate(c,n);
}

求exp:

void exp(modp c[]){
    static modp t[262145],ta[262145];
    t[0]=1;
    for(int k=2;k<=n;k<<=1){
        for(int i=0;i<(k<<1);i++)ta[i]=t[i];
        ln(ta,k);
        for(int i=0;i<k;i++)ta[i]=c[i]-ta[i];
        ta[0].a++;
        NTT(t,k<<1);
        NTT(ta,k<<1);
        for(int i=0;i<(k<<1);i++)t[i]=t[i]*ta[i];
        INTT(t,k<<1);
        for(int i=k;i<(k<<1);i++)t[i]=0;
    }
    memcpy(c,t,sizeof(modp)*n);
}

求冪:
我們在多項式求exp中假定了\(c[0]=1\),那麽如果常數項不是\(1\)的話我們就把常數項變為\(1\)在運算後再用快速冪變回來即可。

void pow(modp c[],int k){
    ln(c);
    for(int i=0;i<n;i++)c[i]=c[i]*k;
    exp(c);
}

8、拉格朗日反演

1.形式冪級數

對於任意一個域\(F\)我們定義在其上的形式冪級數為:\[f(x)=\sum_{k=0}^\infty a_k x^k,a_k\in F\]
記所有的形式冪級數為\(F[[x]]\)

2.反演公式

拉格朗日反演是求關於函數方程的冪級數展開系數非常重要的工具,可以用於組合計數函數的系數提取。

公式內容

這裏\([x^n]f(x)\)指取\(f(x)\)\(x^n\)的系數。

\(f(x),g(x)\in F[[x]]\)\(f(g(x))=x\),則稱\(f(x)\)\(g(x)\)復合逆。滿足:\[[x^n]g(x)=\frac{1}{n}[x^{-1}]\frac{1}{f(x)^n}\tag{1}\]
特別的,如果\(f(x)=\displaystyle\frac{x}{\phi(x)}\),那麽有:\[[x^n]g(x)=\frac{1}{n}[x^{n-1}]\phi(x)^n\tag{2}\]

公式的推導
由式\(f(x)=\displaystyle\frac{x}{\phi(x)}\),得\(\phi(x)=\displaystyle\frac{x}{f(x)}\),代入\((2)\)式可得:\[[x^n]g(x)=\frac{1}{n}[x^{n-1}]\left(\frac{x}{f(x)}\right)^n\]

公式的推廣
\[[x^n]h(g(x))=\frac{1}{n}[x^{n-1}]h‘(x)\left(\frac{x}{f(x)}\right)^n\]

[拉格朗日反演][FFT][NTT][多項式大全]詳解