多项式全家桶 导数公式 $(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})$
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}$
幅角:假设以逆时针为正方向,从xx 轴正半轴到已知向量的转角的有向角叫做幅角
计算:平行四边形法则(其实就是分配律),注意这里的$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$次单位根
单位根的性质与反演 单位根的性质 :
$\omega _n ^k =\cos~k\dfrac{2\pi}{n}+i\times \sin~k\dfrac{2\pi}{n}$
$\omega _{2n}^{2k}=\omega _n^k$
$\omega_n^{k+\frac{n}{2}}=-\omega _n^k$
$\omega_n^0=\omega _n^n=1$
第二条的证明:
$\omega ^{2k}_{2n}=\cos ~2k\dfrac{2\pi}{2n}+i\sin2k\dfrac{2\pi}{2n}$
约分后就和原来一样了
第三条同上,约分即可
单位根反演 :
证明:
当$~k\mid n$ 时:由 $\omega_n^0=\omega_n^n~~~$得:$\omega_n^{ik}=1$故原式等于1
当 $k\nmid n$ 时:原式乘上$\omega_n^k$可化成
②减①得:
易得上式为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^{1-n}$代入$B(x)$得$(z_0,z_1\dots z_{n-1})$,那么有
最下面括号里的式子$\sum_{j=0}^{n-1}(\omega_n^{j-k})^i$显然是能求的
当 $k=j$ 时,该式为$n$
当 $k\ne j$ 时
通过等比数列求和可以得出
所以我们有
因此我们可以发现:
把多项式$A(x)$的离散傅里叶变换结果作为另一个多项式$B(x)$的系数,取单位根的倒数即$\omega ^0_n,\omega ^{−1}_n,\omega ^{−2}_n,…,\omega ^{-(n−1)}_n$作为$x$代入$B(x)$,得到的每个数再除以$n$,得到的就是$A(x)$的各项系数
快速傅里叶变换的数学证明 我们考虑把一个普通的多项式按奇偶性分类,有:
所以我们设成两个多项式:
$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$
我们再代入$x=\omega_n^{k+\frac{n}{2}}$
因此,只要我们求出$A_1(x)$和$A_2(x)$分别在$\omega_{\frac{n}{2}}^0,\omega_{\frac{n}{2}}^1,\omega_{\frac{n}{2}}^2$等的点值表示,就可以$O(n)$的求出$A(x)$的点值表示了。分治的边界是$n=1$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #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 ); 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 )); 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二进制翻转得到的数
那么我们可以据此写出非递归版本$\text{fft}$:先把每个数放到最后的位置上,然后不断向上还原,同时求出点值表示。
蝴蝶操作 貌似也没啥,就是把东西先存上再用,感觉直接看模板就懂了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 #include <iostream> #include <cstdio> #include <algorithm> #include <cstring> #include <cctype> #include <cmath> using namespace std;const int N=4e6 +10 ;const double pi=acos (-1.0 );template <typename T>inline void read (T &x) { x=0 ;int f=0 ;char c=getchar (); for (;!isdigit (c);c=getchar ()) f |= c=='-' ; for (;isdigit (c);c=getchar ()) x=x*10 +(c^48 ); if (f) x=-x; } 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); } int rev[N],b;struct cp { double x,y; cp (){x=0 ,y=0 ;} cp (double xx,double yy){x=xx,y=yy;} }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 (int n,cp *a,int type) { for (int i=0 ;i<n;i++) if (i<rev[i]) swap (a[i],a[rev[i]]); for (int len=1 ;len<n;len<<=1 ){ cp w1 (cos(pi/len),sin(pi/len)*type) ; for (int i=0 ;i<n;i+=(len<<1 )){ cp w (1 ,0 ) ; for (int j=0 ;j<len;j++){ cp x=a[i+j],y=w*a[i+j+len]; a[i+j]=x+y; a[i+j+len]=x-y; w=w*w1; } } } } int n,m,k=1 ;int main () { read (n),read (m); for (int i=0 ;i<=n;i++) read (f[i].x); for (int i=0 ;i<=m;i++) read (g[i].x); while (k<=n+m) k<<=1 ,b++; for (int i=1 ;i<k;i++)rev[i]=(rev[i>>1 ]>>1 )|((i&1 )<<(b-1 )); FFT (k,f,1 ); FFT (k,g,1 ); for (int i=0 ;i<=k;i++) ans[i]=f[i]*g[i]; FFT (k,ans,-1 ); for (int i=0 ;i<=n+m;i++) write ((int )(ans[i].x/k+0.5 ),' ' ); return 0 ; }
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$ 的一个原根
我们设
所以
我们发现,原根包含着单位根的所有性质,所以我们就可以用原根代替单位根
最大优点:保持精度不变
特殊记忆:998244353的原根是3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 #include <iostream> #include <cstdio> #include <algorithm> #include <cstring> #include <cctype> #include <cmath> #define int long long using namespace std;const int N=4e6 +10 ;const int mod=998244353 ;const int G=3 ,invG=332748118 ;template <typename T>inline void read (T &x) { x=0 ;int f=0 ;char c=getchar (); for (;!isdigit (c);c=getchar ()) f |= c=='-' ; for (;isdigit (c);c=getchar ()) x=x*10 +(c^48 ); if (f) x=-x; } 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); } inline int power (int x,int k) { int ans=1 ; while (k){ if (k&1 ) ans=ans*x%mod; x=x*x%mod; k>>=1 ; } return ans; } int rev[N],b;int f[N],g[N],ans[N]; inline void FFT (int n,int *a,int type) { for (int i=0 ;i<n;i++) if (i<rev[i]) swap (a[i],a[rev[i]]); for (int len=1 ;len<n;len<<=1 ){ int w1=power (type==1 ?G:invG,(mod-1 )/(len<<1 )); for (int i=0 ;i<n;i+=(len<<1 )){ int w=1 ; for (int j=0 ;j<len;j++){ int 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)%mod; w=w*w1%mod; } } } if (type==1 ) return ; int ny=power (n,mod-2 ); for (int i=0 ;i<n;i++) a[i]=(a[i]*ny+mod)%mod; } int n,m,k=1 ;signed main () { read (n),read (m); for (int i=0 ;i<=n;i++) read (f[i]); for (int i=0 ;i<=m;i++) read (g[i]); while (k<=n+m) k<<=1 ,b++; for (int i=1 ;i<k;i++)rev[i]=(rev[i>>1 ]>>1 )|((i&1 )<<(b-1 )); FFT (k,f,1 ); FFT (k,g,1 ); for (int i=0 ;i<=k;i++) ans[i]=f[i]*g[i]%mod; FFT (k,ans,-1 ); for (int i=0 ;i<=n+m;i++) write (ans[i],' ' ); return 0 ; }
多项式乘法逆 题目描述 给定一个多项式 $F(x)$ ,请求出一个多项式 $G(x)$, 满足 $F(x)*G(x)\equiv 1\pmod{x^n}$。系数对 $998244353$ 取模。
题解 首先,若 $x$ 只有 $1$ 项时,我们的 $G(x)$ 就是 $x$ 的逆元,不然的话,我们就可以递归求解
我们假设当前已知:
由于
显然:
两式相减,我们就可以得到:
左右同时把 $F(x)$ 去掉,则有
然后我们左右同时平方可以发现:
拆开后有:
再给两边乘上 $F(x)$,化简
我们发现已经用 $H(x)$ 和 $F(x)$ 来代替 $G(x)$ 了。
然后我们递归去找 $H(x)$ 就行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 #include <iostream> #include <cstdio> #include <algorithm> #include <cstring> #include <cmath> #include <queue> #include <cctype> #include <bitset> #include <vector> #include <map> #include <set> #define int long long using namespace std;const int N=3e6 +5 ;const int mod=998244353 ;const int G=3 ,invG=332748118 ;typedef long long ll;namespace scan{ template <typename T> 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> 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); } } using namespace scan;int n,m;int rev[N];int a[N],b[N];int H[N];inline int qpow (int x,int y) { int ans=1 ; while (y){ if (y&1 ) ans=ans*x%mod; x=(x*x)%mod; y>>=1 ; } return ans; } inline void NTT (int n,int *a,int type) { for (int i=0 ;i<n;i++) if (i<rev[i]) swap (a[i],a[rev[i]]); for (int len=1 ;len<n;len<<=1 ){ int w1=qpow (type==1 ? G : invG , (mod-1 )/(len<<1 )); for (int i=0 ;i<n;i+=(len<<1 )){ int w=1 ; for (int j=0 ;j<len;j++){ int 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)%mod; w=w*w1%mod; } } } if (type==1 ) return ; int ny=qpow (n,mod-2 ); for (int i=0 ;i<n;i++) a[i]=(a[i]*ny+mod)%mod; } void GetInv (int *F,int *G,int n) { if (n==1 ) return G[0 ]=qpow (F[0 ],mod-2 ),void (); GetInv (F,G,(n+1 )>>1 ); int len=1 ,L=0 ; while (len < (n<<1 )) len<<=1 ,L++; for (int i=1 ;i<len;i++) rev[i]=(rev[i>>1 ] >> 1 ) | ((i&1 ) << (L-1 )); for (int i=0 ;i<n;i++) H[i]=F[i]; for (int i=n;i<len;i++) H[i]=0 ; NTT (len,H,1 ); NTT (len,G,1 ); for (int i=0 ;i<len;i++) G[i]=(((2ll -G[i]*H[i]%mod)+mod)%mod*G[i]%mod); NTT (len,G,-1 ); for (int i=n;i<len;i++) G[i]=0 ; } signed main () { read (n); for (int i=0 ;i<n;i++) read (a[i]),a[i]=(a[i]+mod)%mod; GetInv (a,b,n); for (int i=0 ;i<n;i++) write (b[i],' ' ); return 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}}$
那么我们此时再积回去,得到最终式子
至于积分和求导的求解,直接看就行
1 2 3 4 5 6 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 ; }
1 2 3 4 5 6 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}$。若有多解,请取零次项系数较小的作为答案。
这个和上面那个差不多吧,顶多就是换个推导过程
假设我们已知:
易知:
我们继续推导可得:
由于题目要求0次项系数最小的最为答案,所以
直接进行多项式求逆和NTT即可
边界:$B_0=\sqrt{A_0}=1$ (二次剩余)
泰勒展开 直接记公式吧:
牛顿迭代 多项式指数函数(多项式 exp) 题目描述 给出 $n-1$ 次多项式 $A(x)$,求一个 $\bmod x^n$ 下的多项式 $B(x)$,满足 $B(x)\equiv e^{A(x)}$。系数对 $998244353$ 取模