1. 程式人生 > 其它 >多項式全家桶!!!

多項式全家桶!!!

多項式全家桶

導數公式

\((C)^{'}=0\) \((x^\mu)'=\mu x^{\mu-1}\)

\((a^x)'=a^x\ln x\) ( \(a\) 為常數) \((\sin x)'=\cos x\)

\((\cos x)'=-\sin x\) \((\tan x)'=\sec^2x\)

\((\cot x)'=-\csc^2x\) \((\sec x)'=\sec x \cot x\)

\((\csc x)'=-\csc x\cot x\) \((\ln x)'=\dfrac{1}{x}\)

\(({\log_a}^x)'=\dfrac{1}{x\ln a}\) \((e^x)'=e^x\)

加減公式:

\((u\pm v)'=u'\pm v'\) \((Cu)'=Cu'\) (C是常數)

\((uv)'=u'v+uv'\) \((\dfrac{u}{v})'=(\dfrac{u'v-uv'}{v^2})\)

複合函式求導:

\(f(g(x))^{'} =f^{'}(g(x))g^{'}(x)\)

對於多個函式的也類似就是這樣

\(f(g(h(x)))^{'}=f^{'}(g(h(x)))g^{'}(h(x))h^{'}(x)\)

我一般記憶成內部不變 ,外部一層一層展開並求導


快速傅立葉變換(FFT)

複數

\(a,b\) 為實數,\(i^2=-1\),形如 \(a+bi\) 的數叫複數,其中 \(i\) 被稱為虛數單位,複數域是目前已知最大的域

在複平面中,\(x\) 代表實數,\(y\) 軸(除原點外的點)代表虛數,從原點 \((0,0)\)\((a,b)\) 的向量表示複數 \(a+bi\)

模長:從原點 \((0,0)\) 到點 \((a,b)\) 的距離,即 \(\sqrt{a^2+b^2}\)

幅角:假設以逆時針為正方向,從 \(x\) 軸正半軸到已知向量的轉角的有向角叫做幅角

計算:平行四邊形法則(其實就是分配律),注意這裡的 \(i^2\)

\(-1\)

幾何定義:複數相乘,模長相乘,幅角相加(至今我也沒看懂)

代數計算方法:\((a+bi)\times (c+di)=ac+bdi^2+bci+adi=ac-bd+(bc+ad)i\)

多項式表示法

係數表示法:

\(A(x)\) 表示一個\(x-1\) 次多項式

\(A(x)=\sum_{i=0}^{n} a_i * x^i\)

例如:\(A(3)=2+3\times x+x^2\)

利用這種方法計算多項式乘法複雜度為 \(O(n^2)\)

(第一個多項式中每個係數都需要與第二個多項式的每個係數相乘)

利用這種方法計算多項式乘法的時間複雜度為 \(O(n^2)\)

點值表示法

\(n\) 互不相同的 \(x\) 帶入多項式,會得到 \(n\) 個不同的取值 \(y\)

則該多項式被這 \(n\) 個點 \((x_1,y_1),(x_2,y_2)\dots(x_n,y_n)\) 唯一確定

其中 \(y_i=\sum_{j=0}^{n-1} a_j\times x_i^j\)

例如:上面的例子用點值表示法可以為 \((0,2)~(1,5)~(2,12)\)

利用這種方法計算多項式乘法的時間複雜度仍然為 \(O(n^2)\)

可以發現,大整數乘法複雜度的瓶頸可能在“多項式轉換成點值表示”這一步(以及其反向操作),只要完成這一步就可以 \(O(n)\) 求答案了。

單位根

定義

在後面我們預設 \(n\) 為 2 的整數次冪

在複平面上,以原點為圓心,1為半徑作圓,所得的圓叫單位圓。以圓點為起點,圓的 \(n\) 等分點為終點,做 \(n\) 個向量,設幅角為正且最小的向量對應的複數為 \(\omega_n\),稱為 \(n\) 次單位根。

根據複數乘法的運演算法則,其餘 \(n-1\) 個複數為 \(\omega_n^2,\omega_n^3\ldots\omega_n^n\)

就算他們的值,我們可以用尤拉公式:\(\omega_{n}^{k}=\cos\ k *\dfrac{2\pi}{n}+i\sin k*\dfrac{2\pi}{n}\)

單位根的幅角為周角的 \(\dfrac{1}{n}\)

在代數中,若\(z^n=1\),我們把\(z\)稱為\(n\)次單位根

單位根的性質與反演

單位根的性質

  1. \(\omega _n ^k =\cos~k\dfrac{2\pi}{n}+i\times \sin~k\dfrac{2\pi}{n}\)
  2. \(\omega _{2n}^{2k}=\omega _n^k\)
  3. \(\omega_n^{k+\frac{n}{2}}=-\omega _n^k\)
  4. \(\omega_n^0=\omega _n^n=1\)
  5. \((\omega _n^k)^2=\omega_n^{2k}\)

第二條的證明:

\(\omega ^{2k}_{2n}=\cos ~2k\dfrac{2\pi}{2n}+i\sin2k\dfrac{2\pi}{2n}\)

約分後就和原來一樣了

第三條的證明:

\[\begin{aligned} \omega_n^{k+\frac n 2} &= \cos (k+\dfrac n 2) \dfrac {2\pi} n + i\times\sin (k+\dfrac n 2) \dfrac {2\pi} n\\ &=\cos (k\dfrac {2\pi} n +\pi)+i\times \sin (k \dfrac {2\pi} n + \pi)\\ &=\cos k\dfrac {2\pi} n\cos \pi - \sin k\dfrac {2\pi} n\sin \pi+i\times(\sin k \dfrac {2\pi}{n} \cos \pi +\cos k\dfrac{2\pi}{n}\sin\pi)\\ &=-\cos k\dfrac {2\pi} n-0+i\times (-\sin k \dfrac {2\pi} n + 0)\\ &=-\omega_{n}^{k} \end{aligned} \]

單位根反演

\[\forall k,[n\mid k]=\dfrac{1}{n}\sum_{i=0}^{n-1}w_n^{ik}\dots① \]

證明:

\(~k\mid n\) 時: 由 \(\omega_n^0=\omega_n^n~~~\) 得:\(\omega_n^{ik}=1\) 故原式等於1

\(k\nmid n\) 時: 原式乘上 \(\omega_n^k\) 可化成

\[\dfrac{1}{n}\sum_{i=1}^{n}w_n^{ik}\dots② \]

②減①得:

\[\dfrac{1}{n}\dfrac{\omega_{n}^{nk}-\omega_n^0}{\omega_n^k-1} \]

易得上式為0

complex

C++的STL提供了複數模板!
標頭檔案:#include <complex>
定義: complex<double> x;
運算:直接使用加減乘除

快速傅立葉變換(FFT)

為什麼要使用單位根作為\(x\)代入

規定我們帶入的點值為 \(n\) 個單位根

\((y_0,y_1,y_2\dots y_{n-1})\) 為多項式 \(A(x)\) 的離散傅立葉變換(即把 \(\omega_n^0,\omega_n^1\dots\omega_n^{n-1}\) 代入上式後的結果)

我們再設一個多項式 \(B(x)\) 其各位係數為上述的 \(y\)

現在,我們把上述單位根的倒數,即 \(\omega_n^{-1},\omega_n^{-2}\dots\omega_n^{-n}\) 代入 \(B(x)\)\((z_0,z_1\dots z_{n-1})\),那麼有

\[\begin{eqnarray} z_k&=&\sum_{i=0}^{n-1}y_i(\omega_n^{-k})^i\\ &=&\sum_{i=0}^{n-1}(\sum_{j=0}^{n-1}a_j(\omega_n^i)^j)(\omega_n^{-k})^i \\&=&\sum_{i=0}^{n-1}a_j(\sum_{j=0}^{n-1}(\omega_n^{j-k})^i) \end{eqnarray} \]

最下面括號裡的式子 \(\sum_{j=0}^{n-1}(\omega_n^{j-k})^i\) 顯然是能求的

\(k=j\) 時,該式為 \(n\)

\(k\ne j\)

通過等比數列求和可以得出

\[\dfrac{(\omega_n^{j-k})^n-1}{\omega_n^{j-k}-1}=\dfrac{1-1}{\omega_n^{j-k}-1}=0 \]

所以我們有

\[z_k=na_k \]

因此我們可以發現:

把多項式\(A(x)\)的離散傅立葉變換結果作為另一個多項式\(B(x)\)的係數,取單位根的倒數即 \(\omega ^0_n,\omega ^{−1}_n,\omega ^{−2}_n,...,\omega ^{-(n−1)}_n\)作為 \(x\) 代入 \(B(x)\),得到的每個數再除以 \(n\),得到的就是 \(A(x)\) 的各項係數

離散傅立葉變換(DFT)的數學證明

我們考慮把一個普通的多項式按奇偶性分類,有:

\[A(x)=(a_0+a_2\times x^2+a_4\times x^4+⋯+a_{n−2}\times x^{n−2})+(a_1\times x+a_3\times x^3+a_5\times x^5+⋯+a_{n−1}\times x^{n−1}) \]

所以我們設成兩個多項式:

\(A_1(x)=a_0+a_2x+a_4x^2+⋯+a_{n−2}x^{\frac{n}{2}-1}\)

\(A_2(x)=a_1+a_3x+a_5x^2+⋯+a_{n−1}x^{\frac{n}{2}-1}\)

因此\(A(x)=A_1(x^2)+xA_2(x^2)\)

假設當前\(k<\frac{n}{2}\),現再我們要代入 \(x=\omega_n^k\)

\[\begin{eqnarray} A(\omega_n^k)&=&A_1(\omega_n^{2k})+\omega_n^kA_2(\omega^{2k}_n) \\&=&A1(\omega_{\frac{n}{2}}^{k})+\omega_n^kA_2(\omega^k_{\frac{n}{2}}) \end{eqnarray} \]

我們再代入 \(x=\omega_n^{k+\frac{n}{2}}\)

\[\begin{eqnarray} A(\omega_n^{k+\frac{n}{2}})&=&A_1(\omega_n^{2k+n})+\omega_n^{k+\frac{n}{2}}A_2(\omega^{2k+n}_n) \\&=&A_1(\omega_{\frac{n}{2}}^{k})-\omega_n^kA_2(\omega^k_{\frac{n}{2}}) \end{eqnarray} \]

因此,只要我們求出 \(A_1(x)\)\(A_2(x)\) 分別在 \(\omega_{\frac{n}{2}}^0,\omega_{\frac{n}{2}}^1,\omega_{\frac{n}{2}}^2\dots\omega_{\frac n 2} ^{\frac n 2}\) 等的點值表示,就可以 \(O(n)\) 的求出 \(A(\omega_n^{1\sim\frac n 2})\) 的點值表示,同時可以得到 \(A(\omega_n^{\frac n2 + 1\sim n})\),正好 \(n\) 個,每個 \(A_1\)\(A_2\) 也這麼求,持續分治,分治的邊界是 \(n=1\)

另外一邊我們是離散傅立葉逆變換(IDFT) 也就這麼理解就行了

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
const int MAXN=2*1e6+10;
inline int read(){
    char c=getchar();int x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}
const double Pi=acos(-1.0);
struct complex{
    double x,y;
    complex (double xx=0,double yy=0){x=xx,y=yy;}
}a[MAXN],b[MAXN];
complex operator + (complex a,complex b){ return complex(a.x+b.x , a.y+b.y);}
complex operator - (complex a,complex b){ return complex(a.x-b.x , a.y-b.y);}
complex operator * (complex a,complex b){ return complex(a.x*b.x-a.y*b.y , a.x*b.y+a.y*b.x);}
void fast_fast_tle(int len,complex *a,int type){
    if(len==1) return ;
    complex a1[len>>1],a2[len>>1];
    for(int i=0;i<=len;i+=2) a1[i>>1]=a[i],a2[i>>1]=a[i|1];
    fast_fast_tle(len>>1,a1,type);
    fast_fast_tle(len>>1,a2,type);
    complex Wn=complex(cos(2.0*Pi/len),type*sin(2.0*Pi/len)),w=complex(1,0);
    for(int i=0;i<(len>>1);i++,w=w*Wn)
        a[i]=a1[i]+w*a2[i],
        a[i+(len>>1)]=a1[i]-w*a2[i];
}
int main(){
    int N=read(),M=read();
    for(int i=0;i<=N;i++) a[i].x=read();
    for(int i=0;i<=M;i++) b[i].x=read();
    int len=1;
    while(len<=N+M) len<<=1;
    fast_fast_tle(len,a,1);
    fast_fast_tle(len,b,1);
    //type為1表示從係數變為點值
    //-1表示從點值變為係數 
    for(int i=0;i<=len;i++) a[i]=a[i]*b[i];
    fast_fast_tle(len,a,-1);
    for(int i=0;i<=N+M;i++) printf("%d ",(int)(a[i].x/len+0.5));//按照我們推導的公式,這裡還要除以n 
    return 0;
}

優化 FFT

遞迴優化

在進行\(\text{fft}\)時,我們要把各個係數不斷分組並放到兩側,那麼一個係數原來的位置和最終的位置有什麼規律呢?

初始位置: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

|隔開各組資料

我們把二進位制拉出來,發現一個位置a上的數,最後所在的位置是a二進位制翻轉得到的數

那麼我們可以據此寫出非遞迴版本 \(\mathrm{FFT}\):先把每個數放到最後的位置上,然後不斷向上還原,我們每次把相鄰的兩塊共同貢獻給上面的那一塊,同時求出點值表示。

蝴蝶操作

貌似也沒啥,就是把東西先存上再用,感覺直接看模板就懂了,差不多得了,還是直接背吧

/*
BlackPink is the Revolution
light up the sky
Blackpink in your area
*/
const int mod = 1e9 + 7;
const int N = 3e6 + 5;
const double Pi = acos(-1.0);
int n, m, T, L, lim, rev[N];
struct cp {
	double x, y;
	cp() {x = 0, y = 0;}
	cp(double a, double b) {x = a, y = b;}
}f[N], g[N], ans[N];
cp operator + (cp a, cp b) {return cp(a.x + b.x, a.y + b.y);}
cp operator - (cp a, cp b) {return cp(a.x - b.x, a.y - b.y);}
cp operator * (cp a, cp b) {return cp(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x);}

inline void FFT(cp *a, int type) {
	rep (i, 0, lim - 1) if (i < rev[i]) swap(a[i], a[rev[i]]);
	for (int len = 1; len < lim; len <<= 1) {
		cp wn(cos(Pi / len), sin(Pi / len) * type);
		for (int i = 0; i < lim; i += (len << 1)) {
			cp w(1, 0), x, y;
			rep (j, 0, len - 1) {
				x = a[i + j], y = w * a[i + j + len];
				a[i + j] = x + y, a[i + j + len] = x - y;
				w = w * wn;
			}
		}
	}
}

int main(){
	read(n, m);
	for (lim = 1; lim <= n + m; lim <<= 1) L++;
	rep (i, 0, n) read(f[i].x);
	rep (i, 0, m) read(g[i].x);
	rep (i, 0, lim - 1) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (L - 1));
	FFT(f, 1);
	FFT(g, 1);
	rep (i, 0, lim) ans[i] = f[i] * g[i];
	FFT(ans, -1);
	rep (i, 0, n + m) write((int)(ans[i].x / lim + 0.5), ' ');
	return 0;
}
//write:RevolutionBP

NTT

\(r,n\) 是互素的整數, \(r \not = 0\)\(n>0\) ,使得 \(r^x\equiv 1 \pmod n\) 成立的最小正整數

\(x\) 稱為 \(r\)\(x\),標為 \(\text{ord}_n r\)

原根

如果 \(r,n\) 都是互素的正整數,當 \(\text{ord}_nr=\varphi(n)\) 時,稱 \(r\) 是模 \(n\) 的原根,即 \(r\)\(n\) 的原根

我們令 \(n\) 為大於 \(1\)\(2\) 的冪,\(p\) 為素數且 \(n \mid (p-1)\)\(g\)\(p\) 的一個原根

我們設

\[g_n=g^{\frac{p-1}{n}} \]

所以

\[\begin{eqnarray} g_n^n&=&g^{n·\frac{p-1}{n}}=g^{p-1} \\g_n^{\frac{n}{2}}&=&g^{\frac{g-1}{2}} \\g_{an}^{ak}&=&g^{\frac{ak(p-1)}{an}}=g^{\frac{k(p-1)}{n}}=g_n^k \end{eqnarray} \]

我們發現,原根包含著單位根的所有性質,所以我們就可以用原根代替單位根

最大優點:保持精度不變

特殊記憶:\(998244353\) 的原根是 \(3\)

/*
BlackPink is the Revolution
light up the sky
Blackpink in your area
*/
const int mod = 998244353;
const int N = 5e6 + 5;
const int G = 114514;
const double Pi = acos(-1.0);
#define int long long

int n, m, T, b, lim, rev[N], f[N], g[N], ans[N];
const int invG = power(G, mod - 2);
inline void FFT(int *a, int type) {
	rep (i, 0, lim - 1) if (i < rev[i]) swap(a[i], a[rev[i]]);
	for (int len = 1; len < lim; len <<= 1) {
		int wn = power(type == 1 ? G : invG, (mod - 1) / (len << 1));
		for (int i = 0; i < lim; i += (len << 1)) {
			int w = 1, x, y;
			rep (j, 0, len - 1) {
				x = a[i + j], y = 1ll * w * a[i + j + len] % mod;
				a[i + j] = (x + y) % mod, a[i + j + len] = (x - y + mod) % mod;
				w = 1ll * w * wn % mod;
			}
		}
	}
    if (type == 1) return ;
    int inv = power(lim, mod - 2);
    rep (i, 0, lim) a[i] = a[i] * inv % mod;
}

signed main(){
	read(n, m);
	for (lim = 1; lim <= n + m; lim <<= 1);
	rep (i, 0, n) read(f[i]);
	rep (i, 0, m) read(g[i]);
	rep (i, 0, lim - 1) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) * (lim >> 1));
	FFT(f, 1), FFT(g, 1);
	rep (i, 0, lim) ans[i] = f[i] * g[i];   
	FFT(ans, -1);
	rep (i, 0, n + m) write(ans[i], ' ');
	return 0;
}
//write:RevolutionBP

多項式乘法逆

題目描述

給定一個多項式 \(F(x)\) ,請求出一個多項式 \(G(x)\), 滿足 \(F(x)*G(x)\equiv 1\pmod{x^n}\)。係數對 \(998244353\) 取模。

題解

首先,若 \(x\) 只有 \(1\) 項時,我們的 \(G(x)\) 就是 \(x\) 的逆元,不然的話,我們就可以遞迴求解

我們假設當前已知:

\[F(x)*H(x)\equiv1\pmod{x^{\lfloor \frac{n}{2} \rfloor}} \]

由於

\[F(x)*G(x)\equiv1\pmod {x^n} \]

顯然:

\[F(x)*G(x)\equiv1\pmod {x^{\lfloor \frac{n}{2} \rfloor}} \]

兩式相減,我們就可以得到:

\[F(x)*(G(x)-H(x))\equiv0\pmod{x^{\lfloor \frac{n}{2} \rfloor}} \]

左右同時把 \(F(x)\) 去掉,則有

\[G(x)-H(x)\equiv0\pmod {x^{\lfloor\frac{n}{2}\rfloor}} \]

然後我們左右同時平方可以發現:

\[(G(x)-H(x))^2\equiv0\pmod{x^{n}} \]

拆開後有:

\[G^2(x)+H^2(x)-2G(x)H(x)\equiv 0\pmod {x^n} \]

再給兩邊乘上 \(F(x)\),化簡

\[F(x)*(G^2(x)+H^2(x)-2G(x)H(x))\equiv0\pmod{x^n} \\G(x)-2H(x)+F(x)H^2(x)\equiv0 \pmod {x^n} \\G(x)\equiv 2H(x)-F(x)H^2(x) \pmod {x^n} \\G(x) \equiv H(x)\times (2-F(x)H(x)) \pmod {x^n} \]

我們發現已經用 \(H(x)\)\(F(x)\) 來代替 \(G(x)\) 了。

然後我們遞迴去找 \(H(x)\) 就行

inline void Inv(ll *X, ll *Y, ll len) {
    if (len == 1) return Y[0] = inv(X[0]), void();
    Inv(X, Y, (len + 1) >> 1);
    for (lim = 1; lim < (len << 1); lim <<= 1);
    static ll H[N];
    rep (i, 0, len - 1) H[i] = X[i];
    rep (i, len, lim - 1) H[i] = 0;
    NTT(H, 1, lim), NTT(Y, 1, lim);
    rep (i, 0, lim)
        Y[i] = ((2ll - Y[i] * H[i] % mod) + mod) % mod * Y[i] % mod;
    NTT(Y, -1, lim);
    rep (i, len, lim - 1) Y[i] = 0;
}

多項式對數函式(多項式 ln)

題目描述

給出 \(n-1\) 次多項式 \(F(x)\) ,求一個 \(\bmod x^n\) 下的多項式 \(G(x)\),滿足 \(G(x)\equiv \ln F(x)\pmod {x^n}\)

首先,我們令 \(f(x)=\ln(x)\)

則原式可化為 \(G(x)\equiv f(F(x))\pmod{x^n}\)

我們對兩邊同時求導有:

\(G'(x) \equiv f'(F(x))F'(x) \pmod {x^{n-1}}\)

由於 \(\ln'(x)=\dfrac{1}{x}\)

\(G'(x)\equiv\dfrac{F'(x)}{F(x)} \pmod{x^{n-1}}\)

那麼我們此時再積回去,得到最終式子

\[G(x)=\int \dfrac{F'(x)}{F(x)} \pmod{x^n} \]

至於積分和求導的求解,直接看就行
求導公式 $$\displaystyle x{a'}=ax{a-1}$$

積分公式$$\displaystyle \int xadx=\frac{1}{a+1}x{a+1}$$

void Direv(int *A,int *B,int len){ //求導
	for(int i=1;i<len;++i) B[i-1]=mul(A[i],i); B[len-1]=0; 
}
void Inter(int *A,int *B,int len){ //積分
	for(int i=1;i<len;++i) B[i]=mul(A[i-1],ksm(i,P-2)); B[0]=0; 
}
void Ln(int *a,int *b,int len){
	Direv(a,A,len),Inv(a,B,len);int l=len<<1;
	NTT(A,1,l),NTT(B,1,l);
	for(int i=0;i<l;++i) A[i]=mul(A[i],B[i]);
	NTT(A,-1,l),Inter(A,b,len);
}

多項式開根

題目描述

給定一個 \(n-1\) 次多項式 \(A(x)\) ,在 \(\bmod x^n\) 意義下的多項式 \(B(x)\) ,使得 \(B^2(x)\equiv A(x) \pmod {x^n}\)。若有多解,請取零次項係數較小的作為答案。

這個和上面那個差不多吧,頂多就是換個推導過程

假設我們已知:

\[H^2(x)\equiv F(x) \pmod {x^{\lceil \frac n 2 \rceil}} \]

易知:

\[G(x)\equiv H(x) \pmod {x^n} \]

我們繼續推導可得:

\[G^2(x)-2H(x)\times G(x)+H^2(x)\equiv 0\pmod{x^n}\\ F(x)-2H(x)\times G(x)+H^2(x)\equiv 0 \pmod{x^n}\\ G(x)\equiv \dfrac{F(x)+H^2(x)}{2H(x)} \pmod {x^n} \]

由於題目要求0次項係數最小的最為答案,所以

\[G(x)=\dfrac{F(x)+H^2(x)}{2H(x)} \]

直接進行多項式求逆和NTT即可

邊界:\(B_0=\sqrt{A_0}=1\) (二次剩餘)


泰勒展開

看了知乎上一個回答,倒是蠻有趣的

首先對於這個函式,我們定義它為 \(f(x)\)

我們現在想做的就是構造一個函式 \(g(x)\) 和它億分相似

首先我們就要找一個點和它重合,哪個點呢?顯然 \((0,1)\) 是最好的選擇

然後我們就會選擇在 \((0,1)\) 處進行 \(n\) 階求導,然後找到一個函式 \(g(x)\) 使得這 \(p(p\in [1,n])\) 階的導數全部和 \(f(x)\)\(p\) 階導數相同,然後我們會考慮,可能一次兩次的,我們願意算,但是太多了肯定就不願意了,所以這個 \(g(x)\) 得長得靠譜,還得好算

所以我們有了一個絕妙的主意,我們用多項式來代替,眾嗦粥之,眾所周知,我們的 \(n\) 次多項式 \(n\) 階求導以後可是個常數啊,這一點就會非常好用

這裡的圖引自知乎,第一條回答(號主已經把號登出了,所以我也沒法確認,如有侵權希望可以和我私聊)

滿足二階導數時,我們的圖長這樣

滿足四階導數時,我們的圖長這樣

很容易想到,如果我們的函式無窮次求導以後,這兩個函式就會無限接近

比如說我們要求 \(\cos 2\),這很難求,但是我們肯定知道 \(\cos \dfrac \pi 2\) 的值,那麼直接進行一個很多次的求導,然後在我的建構函式上就能找到近似值

那麼我們考慮能不能直接用代數式子直接推出來呢?

答案是肯定的,剛才我們說,如果我們 \(n\) 次求導以後兩個函式會無限相似

首先容易得到,如果我們是 \(n\) 次求導,那麼最後我們得到的式子長這樣

\[g(x)=a_0+a_1x+a_2x^2+a_3x^3+\cdots a_nx^n \]

首先容易得到 \(a_0=g(0)=f(0)\)

\(g^n(x)\) 時,原式長成 \(n!a_n\)

容易得到: \(g^n(0)=f^n(0)=n!a_n\)

所以

\[a_n=\dfrac {f^n(0)} {n!} \]

綜上:

\[g(x)=g(0)+\frac{f^{1}(0)}{1!}x+\frac{f^{2}(0)}{2!}x^{2}+……+\frac{f^{n}(0)}{n!}x^{n} \]

若我們不是從 \((0,f(0))\) 開始的,而是從 \((x_0,f(x_0))\) 開始的,那麼這個式子改一改就有了

\[f(x)\approx g(x)=g(x_{0})+\frac{f^{1}(x_{0})}{1!}(x-x_{0})+\frac{f^{2}(x_{0})}{2!}(x-x_{0})^{2}+……+\frac{f^{n}(x_{0})}{n!}(x-x_{0})^{n} \]

\(\mathrm{OI}\) 中,我們很多東西都是有精度限制的,請問我們這個式子什麼時候才能算到我們要的誤差之內呢?

首先我們發現,我們的式子是越來越小的,原因我們可以這樣理解

泰勒展開是先把函式的大框架構建完畢,然後精細化的過程,所以你後面的是基於前面的框架搭建的所以是越來越小,越來越精細的過程

我們會發現,其實我們的式子可遠不止這些,我們後面的式子可以無限拉長,類似這樣:

\[f(x)\approx g(x)=g(x_{0})+\frac{f^{1}(x_{0})}{1!}(x-x_{0})+\frac{f^{2}(x_{0})}{2!}(x-x_{0})^{2}+…+\frac{f^{n}(x_{0})}{n!}(x-x_{0})^{n}+\dfrac {f^{n+1}(x_0)} {(n+1)!}(x-x_0)^{n+1}+\dots+\dfrac {f^{\infin}(x_0)} {\infin!}(x-x_0)^{\infin} \]

假設現在我們把 \(g\) 精確到 \(n\) 階了, 那麼後面的那一堆就是誤差項,有一個叫佩亞諾的倒黴蛋試著去化簡這個誤差項,結果後來啥也沒搞出來,不過他用後面的和前面做了個商,算出來了一個東西,不夠為了紀念他,我們還是將這個誤差項叫作佩亞諾餘項

他算了個這玩意:

\[\dfrac {\huge{誤差項}} {泰勒展開的第~n~項}=\dfrac {f^{n+1}(x_0)} {(n+1)f^n(x_0)}(x-x_0)+\dfrac {f^{n+2}(x_0)} {(n+1)f^n(x_0)}(x-x_0)^2+\cdots \]

然後就是兩位天才的故事拉格朗日和柯西

首先是一個簡單的問題,給你一段時間內汽車走過的路程與時間的關係(\(ST\) 影象),然後我告訴你平均速度是 \(v_1\),然後還告訴你其中某一點的速度是 \(v_0<v_1\),那麼我們很容易知道一定會出現一個點的速度 \(v_2\) 使得 \(v_2>v_1\)

然後這個叫拉格朗日的神仙就直接把這個寫成了一個方程,我們稱其為拉格朗日中值定理:

\[\dfrac {S(t_2)-S(t_1)} {t_2-t_1}=S^{'}(t^{'}) ~~~~~~~~~~t^{'} \in (t1,t2) \]

然後柯西拓展了一下,變成了柯西中值定理

\[\frac{S(t_{2})-S(t_{1})}{T(t_{2})-T(t_{1})}=\frac{S^{'}(t^{'})}{T^{'}(t^{'})} ~~~~~~~~~~t^{'} \in (t1,t2) \]

然後我們迴歸原來我們的誤差項等一系列定義

\[R(x)=\dfrac {f^{n+1}(x_0)} {(n+1)!}(x-x_0)^{n+1}+\dots+\dfrac {f^{\infin}(x_0)} {\infin!}(x-x_0)^{\infin} \]

我們設 \(T(x)=(x-x_0)^{n+1}\),且 \(T(x_0)=R(x_0)=0\)

我們讓等式兩邊同時除以 \(T(x)\),並且使用柯西中值定理,有

\[\dfrac {R(x)} {T(x)}=\dfrac {R(x)-0} {T(x)-0}=\dfrac {R(x)-R(x_0)} {T(X)-T(x_0)}=\dfrac {R^{'} (\xi)} {T^{'}(\xi)}=\dfrac {R^{'} (\xi)} {(n+1)(\xi-x_0)^n} \]

然後我們很容易發現,我們仿照剛才的方式容易得到,上面的那個玩意可以無窮次求導,並且長得和我們剛才的 \(R(x)\) 除了每一項前面加了個係數 \((n+y)\) 以外都一樣,下面的話也是,並且下面除去 \((n+1)\) 以外也一樣,分開寫出來

分子:

\[R(x)=\dfrac {f^{n+1}(x_0)} {(n+1)!}(x-x_0)^{n+1}+\dots+\dfrac {f^{\infin}(x_0)} {\infin!}(x-x_0)^{\infin} \]

所以

\[R^{'}(x)=(n+1)\dfrac {f^{n+1}(x_0)} {(n+1)!}(\xi-x_0)^{n}+(n+2)\dfrac {f^{n+2}(x_0)} {(n+2)!}(\xi-x_0)^{n+1}+\dots+\dfrac {f^{\infin}(x_0)} {\infin!}(\xi-x_0)^{\infin} \]

於是我們可以設 \(P(\xi)=R^{'}(\xi)\)\(P(x_0)=0\)

分母:

我們可以直接設 \(Q(\xi)=T^{'}(\xi)\),且 \(Q(x_0)=0\)

發現原式變成了 \(\dfrac {P(\xi)} {Q(\xi)}\),很巧合我們可以繼續用柯西中值定理歸納

很容易發現我們可以一直這樣歸納最終的結果就會是

\[{誤差項}=\frac{f^{n+1}(\xi)}{(n+1)!}(x-x_{0})^{n+1} \]

這樣的話就非常非常非常好了,我們不用讓 \(x\) 趨近於 \(x_0\),直接就能算出一個點的誤差值

老實說,後面的這一堆東西我們一般也就數學分析的時候用,OI中還是泰勒展開直接應用比較多,原因是生成函式等對於 \(x^n\) 是無效的,所以我們不用考慮這麼多,直接套公式使用即可


牛頓迭代

牛頓迭代就是說,我們給定一個連續的函式 \(f(x)\),然後我們隨便找它上面的一個點,然後作切線,發現這個切線會和 \(x\) 軸有一個交點,然後我們以這個交點作垂線,交函式上一個點,然後 繼續上述操作,我們最終會發現我們的這個和 \(x\) 軸的交點會無限逼近 \(f(x)\)\(x\) 軸的交點

代數方法說明的話就是我們設剛開始函式上這個交點是 \((x_n,f(x_n))\),那麼和 \(x\) 軸交在了 \((x_{n+1},0)\)。我們重複這個過程,如下圖

而且我們順帶說一下切線方程:我們如果已知 \(f(x)=kx+b\),那麼我們的 \(k\) 可以用導數表示出來,即 \(f(x)=f^{'}(x)+b\) 我們稱其為切線方程,對於剛才這個玩意我們也可以用類似這種方式解決,我們要求 \(x_{n+1}\) 的值,即求 $f(x_n)+f^{'}(x_n)\times(x_{n+1}-x_n)=0 $,我們搞一搞:

\[x_{n+1}=x_n-\dfrac {f(x_n)} {f^{'}(x_{n})} \]

同理我們把橫座標及函式轉化成多項式形式,就有 :

\[G(x_0)\equiv G_0(x)-\dfrac{F(G_0(x))}{F^{'}(G_0(x))} \]

多項式指數函式(多項式 exp)

題目描述

給出 \(n-1\) 次多項式 \(A(x)\),求一個 \(\bmod x^n\) 下的多項式 \(B(x)\),滿足 \(B(x)\equiv e^{A(x)}\)。係數對 \(998244353\) 取模

首先對於兩邊同時取 \(\ln\),沒什麼好說的

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

我們令 \(F(G(x))=\ln B(x)- A(x)\equiv 0\pmod {x^n}\)

並且我們容易得到:

\[F^{'}(G(x)) = \dfrac {1} {G(x)} \]

代入牛頓迭代以後得到:

\[G(x)\equiv G_0(x)-\dfrac {\ln G_0(x)-F(x)} {\frac 1 {G(x)}}\pmod {x^n} \]

化簡後得:

\[G(x)\equiv G_0(x)(1-\ln G_0(x)+F(x)) \pmod {x^n} \]

到這裡,最簡單的多項式已經都有了,剩下的也是這些的拓展和加深,所以我們可以放出vector封裝好的多項式模板了

/*
Blackpink is the Revolution
light up the sky
Blackpink in your area
*/
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cctype>
#include <bitset>
#include <vector>
#include <cstdio>
#include <cmath>
#include <queue>
#include <ctime>
#include <map>
#include <set>

using namespace std;
using ll = long long;
using P = pair<int ,int>;
using poly = vector <ll>;

namespace scan {
template <typename T>
inline void read(T &x) {
    x = 0; char c = getchar(); int f = 0;
    for (; !isdigit(c); c = getchar()) f |= (c == '-');
    for (; isdigit(c); c=getchar()) x = x * 10 + (c ^ 48);
    if (f) x = -x;
}
template <typename T, typename ...Args>
inline void read(T &x, Args &...args) {
    read(x), read(args...);
}
template <typename T>
inline void write(T x, char ch) {
    if (x < 0) putchar('-'), x = -x;
    static short st[30], tp;
    do st[++tp] = x % 10, x /= 10; while(x);
    while (tp) putchar(st[tp--] | 48);
    putchar(ch);
}
template <typename T>
inline void write(T x) {
    if (x < 0) putchar('-'), x = -x;
    static short st[30], tp;
    do st[++tp] = x % 10, x /= 10; while(x);
    while(tp) putchar(st[tp--] | 48);
}
inline void write(char ch){
    putchar(ch);
}       
template <typename T, typename ...Args>
inline void write(T x, char ch, Args ...args) {
    write(x, ch), write(args...);
}
} //namespace scan
using namespace scan;

#define rep(i, a, b) for(ll i = (a); (i) <= (b); ++i)
#define per(i, a, b) for(ll i = (a); (i) >= (b); --i) 

const ll mod = 998244353;
const int N = 2e6 + 5;
ll n, m, T;

namespace Poly {
#define Size(_) int(_.size())
#define lg(x) ((x) == 0 ? -1 : __lg(x))
const ll Primitive_Root = 114514;
const ll invPrimitive_Root = 137043501;
poly rev;

inline ll qpow(ll x, ll k) {
    ll res = 1;
    while (k) {
        if (k & 1) res = res * x % mod;
        x = x * x % mod;
        k >>= 1;
    }
    return res;
}
inline ll inv(ll x) {return qpow(x, mod - 2);}


inline void NTT(ll *a, ll lim, ll type) {
    rev.resize(lim);
    rep (i, 0, lim - 1) {
        rev[i] = rev[i] = (rev[i >> 1] >> 1) | ((i & 1) * (lim >> 1));
        if (i < rev[i]) swap(a[i], a[rev[i]]);
    }
    for (ll len = 1; len < lim; len <<= 1) {
        ll wn = qpow(type == 1 ? Primitive_Root : invPrimitive_Root, (mod - 1) / (len << 1));
        for (ll i = 0; i < lim; i += (len << 1)) {
            ll w = 1, x, y;
            rep (j, 0, len - 1) {
                x = a[i + j] % mod, y = w * a[i + j + len] % mod;
                a[i + j] = (x + y) % mod, a[i + j + len] = (x - y + mod) % mod;
                w = w * wn % mod;
            }
        }
    }
    if (type == 1) return ;
    ll inv_len = inv(lim);
    rep (i, 0, lim - 1) a[i] = a[i] * inv_len % mod;
}

poly operator * (poly F, poly G) {
	ll siz = Size(F) + Size(G) - 1, lim = (1 << (lg(siz - 1) + 1));
	if (siz <= 300) {
		poly H(siz);
		per (i, Size(F) - 1, 0) 
			per (j, Size(G) - 1, 0)
				H[i + j] = (H[i + j] + F[i] * G[j] % mod) % mod;
		return H;
	}
	F.resize(lim), G.resize(lim);
	NTT(F.data(), lim, 1);
	NTT(G.data(), lim, 1);
	rep (i, 0, lim - 1) F[i] = F[i] * G[i] % mod;
	NTT(F.data(), lim, -1);
	F.resize(siz);
	return F;
}

poly operator + (poly F, poly G) {
	int siz = max(Size(F), Size(G));
	F.resize(siz);
	G.resize(siz);
	rep (i, 0, siz - 1) F[i] = F[i] + G[i] % mod;
	return F;
}

poly operator - (poly F, poly G) {
	int siz = max(Size(F), Size(G));
	F.resize(siz);
	G.resize(siz);
	rep (i, 0, siz - 1) F[i] = (F[i] - G[i] + mod) % mod;
	return F;
}

poly cut(poly F, ll len) {
	F.resize(len);
	return F;
}

poly Direv(poly F) {
	int siz = Size(F) - 1;
	rep (i, 0, siz - 1) F[i] = F[i + 1] * (i + 1) % mod;
	F.pop_back();
	return F;
}

poly inter(poly F) {
	F.emplace_back(0);
	per (i, Size(F) - 1, 0) F[i] = F[i - 1] * inv(i) % mod;
	F[0] = 0;
	return F;
}

poly Inv(poly F) {
	int siz = Size(F), lim = (1 << (lg(siz - 1) + 1));
	poly G;
	G.resize(1);
	G[0] = inv(F[0]);
	F.resize(lim);
	for (int len = 2; len <= lim; len <<= 1) {
		poly tmp(len << 1, 0);
		rep (i, 0, len - 1) tmp[i] = F[i];
		G.resize(len << 1);
		NTT(tmp.data(), len << 1, 1), NTT(G.data(), len << 1, 1);
		rep (i, 0, (len <<  1) - 1)
			G[i] = G[i] * (mod + 2ll - tmp[i] * G[i] % mod) % mod;
		NTT(G.data(), (len << 1), -1);
		G.resize(len);
	}
	G.resize(siz);
	return G;
}
	
poly ln(poly F) {
	int siz = Size(F);
	return cut(inter(cut(Direv(F) * Inv(F), siz)), siz);
}

poly exp(poly F) {
	int siz = Size(F);
	poly G{1};
	for (int i = 2; (i >> 1) < siz; i <<= 1) {
		G = G * (poly{1} - ln(cut(G, i)) + cut(F, i));
		G.resize(i);
	}
	G.resize(siz);
	return G;
}

}// namespace Poly
using namespace Poly;

namespace RevolutionBP {
ll lim, L;
void main() {
	read(n);
	poly F(n);
	for (auto& i : F) read(i);
	F = Poly::ln(F);
	for (auto& i : F) write(i, ' ');
    return void();
}
}
signed main(){
    RevolutionBP::main();
    return 0;
}
//write: RevolutionBP