多项式:从什么都不知道到门都没入

多项式

单项式:由数字和字母组成的积的代数式称为单项式

多项式:由若干个单项式相加组成的代数式叫做多项式

废话,这上过初中的人都知道

一、一元多项式

\(n\in N^*\),则我们称多项式
\[ a_nx^n+a_{n-1}x^{n-1}+...+a_1x+a_0 \]
一元多项式

其中,我们称\(a_ix^i\)\(i\)次项,\(a_i\)\(i\)次项系数。

两个多项式\(f(x)\)\(g(x)\)相等,当且仅当它们同次项的系数相等。

\(a_n \not= 0\),则我们称\(a_n\)为多项式的首项,\(n\)为多项式的次数,记为\(deg\) \(f\)

二、 多项式的基本运算

1.加法与乘法运算


\[ f(x)=a_nx^n+a_{n-1}x^{n-1}+...+a_1x+a_0\\ g(x)=b_nx^n+b_{n-1}x^{n-1}+...+b_1x+b_0 \]

\[ f(x)+g(x)=(a_n+b_n)x^n+(a_{n-1}+b_{n-1})x^{n-1}+...+(a_1+b_1)x+a_0+b_0\\ f(x)\times g(x)=a_nb_nx^{2n}+...+\sum_{i=0}^n a_{n-i}b_i x^n+...+a_1b_1x+a_0b_0 \]
多项式的加法和乘法满足交换律和结合律,乘法还满足分配律

2.带余除法

\(f(x)\)\(g(x)\)是的两个多项式,且\(g(x)\)不等于0,则有唯一的多项式 \(q(x)\)\(r(x)\),满足
\[ f(x)=q(x)g(x)+r(x) \]
其中\(r(x)\)的次数小于\(g(x)\)的次数。

此时,

\(q(x)\) 称为\(g(x)\)\(ƒ(x)\)的商式。

\(r(x)\)称为余式。

\(r(x)=0\),则我们称\(g(x)\)\(f(x)\)的因式

多项式的带余除法不是本文研究的重点,有兴趣的可以自行查阅资料

三、关于多项式的几个重要定理

1.代数基本定理

任何复系数一元n次多项式方程在复数域上至少有一根。

代数基本定理在数学中的运用十分广泛。

2.唯一分解定理

数域\(P\)上的每个次数大于零的多项式\(f(x)\)都可以分解为数域\(P\)上的不可约多项式的乘积

这与算术基本定理十分相似。

3.高斯引理

本原多项式:若多项式\(a_nx^n+a_{n-1}x^{n-1}+...+a_1x+a_0\)满足\(gcd(a_1,a_2,...,a_n)=1\),则我们称该多项式为本原多项式

高斯引理: 两个本原多项式的积是本原多项式

这些引理的应用是数学竞赛的范围了,有兴趣的可以自行了解。

四、多项式的零点

若有一数\(a\)使得多项式\(f(x)=0\),则我们称\(a\)为多项式\(x\)的零点。

这里我们有两个重要的定理:

1.余式定理

\(f(x)\)除以\(x-a\)的余式是\(f(a)\)

证明:
\(g(x)\)为商式,\(r(x)\)为余式,则我们有:
\[ f(x)=(x-a)g(x)+r(x) \]
\(x=a\)代入得:
\[ f(a)=(a-a)g(a)+r(a)=r(a) \]
证毕。

余式定理的几个推论:

1.因式定理

如果多项式\(f(a)=0\),那么多项式\(f(x)\)必定含有因式\(x-a\)

2.多项式\(a_nx^n+a_{n-1}x^{n-1}+...+a_1x+a_0\)至多有\(n\)个不同的零点。

用反证法可以证明,这里略去。

3.如果多项式\(a_nx^n+a_{n-1}x^{n-1}+...+a_1x+a_0\)至少有\(n+1\)个零点,那么该多项式为\(0\)

如果此时他不是\(0\),则他至多有\(n\)个零点,由(2)导出矛盾。

2.恒等定理

若有无穷多个\(a\)使得\(f(a)=g(a)\),则\(f(x)=g(x)\)

\(h(x)=f(x)-g(x)\),因为此时\(h(x)\)有无穷多个零点,由上面的第3个结论可知,\(h(x)=0\)

看到这里,可能很多人不耐烦了:

我们是OIer,你给我们讲那么多这些玩意干什么?

如果上面是纯数学竞赛内容的话,那么下面就是数竞与信竞的交集了。

--------------------------------------------------------------分割线------------------------------------------------------------------------------------

五、多项式的插值与差分

(注意:本章将涉及到一些OI中的知识,数竞党看不懂可略过)

我们回到多项式乘法。

如果我们暴力乘两个\(n\)次多项式,那么时间复杂度将是\(O(n^2)\)

那么我们可不可以对一般的多项式乘法进行些优化呢?

我们可以通过改变运算顺序(CPU的运算原理相关)来优化,但我们最多只能优化些常数因子罢了

如果我们不能跳出上面的思维局限,那么我们不可能在根本上优化算法的时间复杂度

我们得换个角度观察问题。

在平面直角坐标系中,我们给出平面上\(n\)个不同的点:
\[ (x_1,y_1),(x_2,y_2),...,(x_n,y_n) \]
那么,我们能否找到一条曲线,使得该曲线穿过这些点呢?

换言之,我们希望找到一个函数\(f(x)\),使得:
\[ f(x_1)=y_1\\ f(x_2)=y_2\\ ...\\ f(x_n)=y_n \]
但显然,这样的函数有无数个。

一般而言,多项式函数是这些函数中最简单的。

那么,多项式就有另外一种表示法:

多项式的点值表示法

我们用满足\(f(x)=y\)\(n\)个数对
\[ (x_1,y_1),(x_2,y_2),...,(x_n,y_n) \]
来表示一个\(n-1\)次多项式。

如果我们给定一个多项式,给出\(n\)组点值最简单的方法就是任选\(n\)个不同的\(x_i\),分别计算此时多项式的值

\(n\)个点值对也可以唯一确定一个不超过\(n-1\)次多项式,这个过程称为插值

于是,一个著名的公式出现了:

拉格朗日插值公式

对于多项式的点值
\[ (x_0,y_0),(x_1,y_1),...,(x_{n-1},y_{n-1}) \]
存在一个唯一的不超过\(n-1\)次的多项式\(f(x)\),并且\(f(x)\)可以表示为:
\[ f(x)=y_0\frac{(x-x_1)(x-x_2)...(x-x_{n-1})}{(x_0-x_1)(x_0-x_2)...(x_0-x_{n-1})}\\ +y_1\frac{(x-x_0)(x-x_2)...(x-x_{n-1})}{(x_1-x_0)(x_1-x_2)...(x_2-x_{n-1})}+...\\ +y_{n-1}\frac{(x-x_0)(x-x_1)...(x-x_{n-2})}{(x_{n-1}-x_0)(x_{n-1}-x_1)...(x_{n-1}-x_{n-2})} \]
整理的好看点:
\[ f(x)=\sum_{i=0}^{n-1} y_i \times \prod_{0\leq i\not=j\leq n-1} \frac{x-x_j}{x_i-x_j} \]
为了证明插值公式,我们必须证明多项式插值的存在性和唯一性

存在性证明:

(这部分涉及一些线性代数的知识,如果实在不会可以略过)

(估计也只有我这种蒟蒻不怎么会线性代数)

我们可以把多项式的每个点值看成一个方程,那么我们就有了\(n\)个方程,它们组成了线性方程组。

我们把这个线性方程组改写成矩阵的形式:
\[ \begin{bmatrix} 1&x_0& x_0^2 &\cdots & x_0^{n-1} \\ 1& x_1& x_1^2 &\cdots & x_1^{n-1}\\ \vdots& \vdots& \vdots & \ddots & \vdots\\ 1& x_{n-1} & x_{n-1}^2& \cdots & x_{n-1}^{n-1} \end{bmatrix} \begin{bmatrix} a_0\\\ a_1\\ \vdots\\ a_{n-1} \end{bmatrix}= \begin{bmatrix} y_0 \\ y_1 \\ \vdots \\ y_{n-1} \end{bmatrix} \]
我们不妨记为\(XA=Y\)

该式可以转化为\(A=X^{-1}Y\)

因此我们只需要证明\(X\)有没有逆矩阵即可。

Q:什么是逆矩阵啊

A:若我们有\(AB=E\)\(E\)为单位矩阵),则我们称\(B\)\(A\)矩阵的逆

Q:单位矩阵又是啥啊

A:主对角线上全是1的举证就是单位矩阵。单位矩阵有一个性质,即任何矩阵乘单位矩阵都等于它本身

这里我们介绍一个东西:范德蒙行列式

线代julao可略过

长成上面这样的矩阵就是范德蒙矩阵了。

而范德蒙行列式是有它的特殊的计算公式的。

对于一个范德蒙矩阵,它的行列式的计算公式为:

\[ det(X)=\prod _{1\leq i<j\leq n} (x_j-x_i) \]

显然,\(x_j-x_i\not=0\),所以矩阵的行列式值不等于0,即该矩阵有逆

唯一性证明:

如果有两个这样的多项式,那么它们的差就至少有\(n\)个不同的零点(点值中\(y\)值差为0),由上面余式定理的第三个推论可得,这个差值一定为0

正确性证明:

现在我们来看看拉格朗日插值公式是怎么构造这个多项式的。

在特殊情形\(y_1=y_2=...=y_{n-1}=0\)时,求出满足条件多项式\(f_1(x)\)。这时,\(f_1(x)\)\(n-1\)个不同的零点\(x_1,x_2,...,x_{n-1}\),根据因式定理,该多项式被\((x-x_1)(x-x_2)...(x-x_{n-1})\)整除。

我们有要求多项式的次数尽可能的低,故我们得到:
\[ f_1(x)=c(x-x_1)(x-x_2)...(x-x_{n-1}) \]
其中\(c\)是一个常数,由\(f_1(x_0)=y_0\)确定。

我们就有:
\[ f_1(x_0)=y_0=c(x_0-x_1)(x_0-x_2)...(x_0-x_{n-1}) \]
所以
\[ c=\frac{y_0}{(x_0-x_1)(x_0-x_2)...(x_0-x_{n-1})} \]
于是
\[ f_1(x)=y_0\frac{(x-x_1)(x-x_2)...(x-x_{n-1})}{(x_0-x_1)(x_0-x_2)...(x_0-x_{n-1})} \]
类似的,我们可以构造\(f_i(x)\),使得\(f_i(x_i)=y_i\)

显然,当\(x\not=i\)时(\(x\)\(x_0,x_1,...x_{n-1}\)中的任意一个),\(f_i(x)=0\)(因为分子总有一个因式值为0)

所以,这样的\(f(x)=f_1(x)+f_2(x)+...f_n(x)\)满足条件。

优缺点

拉格朗日插值公式结构紧凑,十分优美,易于理解,在理论分析中十分方便。

然而,当一个插值点改变时,整个插值公式必须要重新计算。

解决方案就是使用重心拉格朗日插值法或者牛顿迭代法。

然而我太菜了,这两个方法我都不会。

还是得多多学习啊!

插值介绍完了,现在轮到差分了。

广大OIer对差分的影响,估计是这样的:

差分不就是和前缀和结合在一起用的东西吗!

树状数组区间修改用差分!(注:局限性很大)

树上差分!天天爱跑步!(明明线段树合并就解决了

多项式差分正是差分众多形式中的一种。

对于函数\(f(x)\)和固定的\(h\not=0\)
\[ f(x+h)-f(x) \]
称为\(f(x)\)步长为\(h\)的一阶差分,记作\(\Delta_h^1f(x)\)\(\Delta_h^1f(x)\)的差分

\[ \begin{align} \Delta_h^1f(x+h)-\Delta_h^1f(x)&=(f(x+2h)-f(x+h))-(f(x+h)-f(x))\\ &=f(x+2h)-2f(x+h)+f(x) \end{align} \]

称为\(f(x)\)的(步长为\(h\))的二阶差分,记作\(\Delta_h ^2f(x)\)

对于\(f(x)\)\(n\)阶差分,我们有以下公式:
\[ \Delta _h ^n f(x)=\sum_{i=0}^n(-1)^{n-i}C_n^if(x+ih) \]
上述公式在\(n=1\)时显然成立,于是我们可以结合数学归纳法证明,此处略去。

讲了这么多,一点代码也没有,总该来道题吧!

于是模板题来了!(数竞党慎入)

P4781【模板】拉格朗日插值法

直接上代码吧:

#include<bits/stdc++.h>
using namespace std;
const int maxn=2005;
const int MOD=998244353;
typedef long long ll;
int n,k;
ll x[maxn];
ll y[maxn];
inline ll ksm(ll x,int y){
    if(!y) return 1;
    if(y==1) return x%MOD;
    ll tmp=ksm(x,y/2);
    tmp=(tmp*tmp)%MOD;
    if(y&1) return (tmp*x)%MOD;
    else return tmp; 
}
int main(){
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++) scanf("%lld%lld",&x[i],&y[i]);
    ll ans=0;
    for(int i=1;i<=n;i++){
        ll s1=y[i]%MOD; 
        ll s2=1;
        for(int j=1;j<=n;j++) if(i!=j){
            s1=(s1*(k-x[j]))%MOD;
            s1=(s1+MOD)%MOD;
            s2=(s2*(x[i]-x[j]))%MOD;
            s2=(s2+MOD)%MOD;
        }
        ans=(ans+(s1*ksm(s2,MOD-2))%MOD)%MOD;
        ans=(ans+MOD)%MOD;
    }
    printf("%lld\n",ans);
    return 0;
}

六、多项式乘法的优化

我们先前讨论过多项式的点值表示法。

普通的多项式乘法是\(O(n^2)\)的,但如果我们换成点值表示法呢?

现在我们有两个多项式\(f(x)\)\(g(x)\),记它们的积为\(r(x)\)

则对于同样的\(a\),它们分别有点值\((a,y_f)\)\((a,y_g)\)\((a,y_r)\)

显然,\(y_r=y_f*y_g\)

所以如果我们对于每个参与乘法的多项式有\(n\)个点值的话,我们可以在\(O(n)\)的时间内完成乘法运算!

于是,对于系数表示法的多项式的乘法运算,我们可以尝试用点值表示法优化。

过程如下:

1.将系数表达法的多项式改写为点值表示法的多项式(点值过程

2.用点值表示法计算多项式的乘法。

3.将点值表示法转化为系数表示法。(插值过程

下面,我们重点介绍完成这一过程的算法。

七、DFT,IDFT与FFT

DFT:离散傅里叶变换,用来完成点值过程。

IDFT:离散反傅里叶变换,用来完成插值过程。

FFT:DFT和IDFT的高效算法。

前置知识

1.复数

我们把形如\(a+bi(a,b\in R)\)的数称为复数,其中\(i\)为虚数单位,规定
\[ i^2=-1 \]
其中\(a\)被称为实部,\(b\)被称为虚部。

知道这玩意有啥用?(我还是个初中的蒟蒻

一会你就知道了。

我们大家都知道笛卡尔发明的万恶的平面直角坐标系。

对于复数,我们有一个类似的东西:复平面

复平面的横轴是实数轴,纵轴是虚数轴。

于是对于每一个复数,我们都有一个唯一对应的向量了。

我们可以用\((r,\theta)\)来表示一个虚数,其中\(r\)为虚数的长度,\(\theta\)为该向量与\(x\)轴正半轴的夹角。

复数也是有它的四则运算的。

对于两个复数\(z_1=a+bi\)\(z_2=c+di\),它们的和 和 积为:
\[ z_1+z_2=(a+c)+(b+d)i\\ z_1\times z_2=(a+bi)(c+di)=(ac-bd)+(ad+bc)i \]
复数相加和向量加法一样,满足平行四边形法则

对于乘法,如果用坐标表示的话,我们有:
\[ (r_1,\theta_1)\times(r_2,\theta_2)=(r_1\times r_2,\theta_1+\theta_2) \]
即传说中的模长相乘,辐角相加

所以如果两个复数的长度都是1,那他们的乘法就知识单纯的角度相加了。

2.单位根

对于我们的点值过程,我们当然可以随便取几个值代入去算。

但显然,暴力计算\(x,x^2,x^3...,x^n\)当然是\(O(n^2)\)的。

我们可不可以不计算这些呢?

我们当然希望这些数是\(1,-1\)这样的数,因为它们的若干次方是1。但明显,这不够。

傅里叶:这个圆上的每个点都可以。

没错,这个圆是单位圆

我们可以把这个圆\(n\)注意:\(n\)是2的幂次)等分,从\((1,0)\)开始,编号为\(0\)\(n-1\)。我们记编号为\(k\)的点为\(w_n^k\)

显然,\((w_n^1)^k=w_n^k(k\not=0)\)(模长相乘,辐角相加)。

我们把\(w_n^1\)称为\(n\)次单位根,简单记为\(w_n\)

而这些\(w_n^0,w_n^1,w_n^2,...,w_n^{n-1}\)有个极其重要的性质:它们的\(n\)次方等于1!

下面我们就要来证明这个性质:

对于\(w_n^0\),它恒等于1。

而对于大于0的正整数\(k\)而言,\(w^k_n=(w^1_n)^k\)

所以只要证明\(w_n\)\(n\)次方等于1即可。

由于我们是把单位圆\(n\)等分,我们很容易得到(注意这是单位圆):
\[ w_n=\cos \frac{2\pi}{n}+i\sin \frac{2\pi}{n} \]
由欧拉公式
\[ e^{ix}=\cos x+i\sin x \]
\(x\)\(\frac{2\pi}{n}\)
\[ w_n=e^{\frac{2\pi i}{n}} \]
所以
\[ (w_n)^n=(e^{\frac{2\pi i}n{}})^n=e^{2\pi i} \]
欧拉公式中的\(x\)\(2\pi\)
\[ e^{2\pi i}=\cos 2\pi +i \sin 2\pi =1 \]
所以
\[ (w_n)^n=1 \]
证毕。

单位根还有以下两个性质:

1.\(w_{2n}^{2k}=w_n^k\)

它们表示的复数(向量)是相同的,可以感性理解以下。

2.\(w_n^{k}=-w_{n}^{k+\frac{n}{2}}\)

它们表示的向量等大而且反向(\(\frac{n}{2}\)表示二分之一个圆周)

上面两个性质可以结合欧拉公式给出证明,但我觉得证明了反而不是那么好理解了。

快速傅里叶变换(FFT)

有了单位根,我们就可以在DFT的过程中减少次方运算了。

但对于不是\(2\)的幂次的项,还是要我们暴力乘出来,总体的时间复杂度依然是\(O(n^2)\)的。

这时,FFT闪亮登场!

第一步:离散傅里叶变换(DFT)

现在我们有一个\(n\)项的多项式(同样地,\(n\)是2的幂次,且这里\(n\ge2\)):
\[ f(x)=a_0+a_1x+a_2x^2+...+a_{n-1}x^{n-1} \]
我们现在根据奇偶性把这个多项式分为两半:
\[ f(x)=(a_0+a_2x^2+...+a_{n-2}x^{n-2})+(a_1x+a_3x^3+...+a_{n-1}x^{n-1})\\ =(a_0+a_2x^2+...+a_{n-2}x^{n-2})+x(a_1+a_3x^2+...+a_{n-1}x^{n-2}) \]
我们再令:
\[ g(x)=a_0+a_2x+a_4x^2+...+a_{n-2}x^{\frac{n}{2}-1}\\ r(x)=a_1+a_3x+a_5x^2+...+a_{n-1}x^{\frac{n}{2}-1} \]
\(x^2\)代入\(g(x),r(x)\)
\[ g(x^2)=a_0+a_2x^2+a_4x^4+...+a_{n-2}x^{n-2}\\ r(x^2)=a_1+a_3x^2+a_5x^4+...+a_{n-1}x^{n-2} \]
所以
\[ f(x)=g(x^2)+x\times r(x^2) \]
我们设\(k<\frac{n}{2}\),代入\(w_n^k\)得:
\[ f(w_n^k)=g((w_n^k)^2)+w_n^k\times r((w_n^k)^2)\\ =g(w_n^{2k})+w_n^k\times r(w_n^{2k})\\= g(w_{\frac{n}{2}}^k)+w_n^k\times r(w_{\frac{n}{2}}^k) \]
我们再代入\(w_n^{k+\frac{n}{2}}\)
\[ f(w_n^{k+\frac{n}{2}})=g((w_n^{k+\frac{n}{2}})^2)+w_n^{k+\frac{n}{2}}\times r((w_n^{k+\frac{n}{2}})^2)\\ =g(w_n^{2k+n})+w_n^{k+\frac{n}{2}}\times r(w_n^{2k+n})\\ =g(w_n^{2k}w_n^n)+w_n^{k+\frac{n}{2}}\times r(w_n^{2k}w_n^n)\\ =g(w_{\frac{n}{2}}^k)-w_n^k\times r(w_{\frac{n}{2}}^k) \]
以上两式常被称为蝴蝶操作

我们于是得到了一个结论:

如果我们知道\(g(x)\)\(r(x)\)\(w_{\frac{n}{2}}^0,w_{\frac{n}{2}}^1,...,w_{\frac{n}{2}}^{n-1}\)处的值,那么我们就可以在线性时间内求出\(f(x)\)\(w_n^0,w_n^1,...,w_n^{n-1}\)处的值。

我们再对\(g(x)\)\(r(x)\)进行相同的操作,于是我们就可以这么递归分治向下求解了。

注意:我们只能对长度为\(2\)的次幂的数列进行分治FFT,否则递归向下的话两边长度就不相等了。

一般对于这种情况,我们可以在第一次DFT之前把序列长度补成2的次幂。对于新补的高次项,我们令他们的系数为0。

这样,算法的时间复杂度就是\(O(n\log n)\)了。

但是,我相信大家都知道,递归过程是很耗内存的。

我们能不能不递归就进行FFT呢?

当然可以!

我们完全可以在原序列里把这些系数拆分了,然后在“倍增”地合并。

现在的关键问题就是我们如何拆分这些系数了。

这里我盗张图

有啥规律呢?

对于原序列中的系数\(a_i\),令\(i\)做二进制位翻转操作后的结果为\(rev(i)\)(比如说:在长度为8的情况下,1就是001,翻转之后就是100,即4),那么\(a_i\)最后的位置就是\(rev(i)\)

第二步:离散反傅里叶变换(IDFT)

IDFT的过程的作用我们先前说过了,就是要完成插值过程。

我们把单位根代入,就得到下面的线性方程组的等效矩阵:
\[ \begin{bmatrix} 1& 1 &1 &\cdots& 1\\ 1& w_n^1& w_n^2&\cdots &w_n^n\\ 1& w_n^2& w_n^4&\cdots &w_n^{2n}\\ \vdots& \vdots &\vdots &\ddots &\vdots \\ 1& w_n^{n-1}&w_n^{2n-2}&\cdots & w_n^{(n-1)^2} \end{bmatrix} \begin{bmatrix} a_0\\ a_1\\ a_2\\ \vdots\\ a_{n-1} \end{bmatrix} = \begin{bmatrix} y_0\\ y_1\\ y_2\\ \vdots\\ y_{n-1} \end{bmatrix} \]
我们仍然记为\(XA=Y\)

相当于我们现在知道了\(X,Y\)去求\(A\)了。

我们前面在探究多项式插值的时候,我们已经说明了多项式插值的存在性与唯一性(忘了可以重新看一眼)。

现在,\(A=X^{-1}Y\),我们前面已经说明\(X\)存在逆矩阵,于是问题的关键就是求这个逆矩阵了。

我们知道,\(X\)是范德蒙矩阵,它的行列式有它的特殊计算方式,而它的逆矩阵也有他独特的性质,就是每一项取倒数,再除以\(n\)(为什么是这样,我不会证

我们在先前探讨单位根的时候,我们知道:
\[ w_n=e^{\frac{2\pi i}{n}} \]
我们现在取倒数,即-1次方:
\[ w_n^{-1}=e^{\frac{-2\pi i}{n}} \]
于是我们只要把\(\pi\)取成\(-3.1415926...\),然后其他过程和DFT是一样的!

所以,我们只需要一个函数,就可以完成DFT与IDFT两个操作了。

好了,放上一道模板题:P3803 【模板】多项式乘法(FFT)

Code:

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
const double Pi=acos(-1.0);
struct Complex{
    double x,y;
    Complex(double a=0,double b=0){
        x=a; y=b;
    }
    Complex operator +(const Complex a)const{
        return Complex(x+a.x,y+a.y);
    } 
    Complex operator *(const Complex a)const{
        return Complex(x*a.x-y*a.y,x*a.y+y*a.x);
    }
    Complex operator -(const Complex a)const{
        return Complex(x-a.x,y-a.y);
    }
}a[maxn<<1],b[maxn<<1];
int n,m;
int len;
int r[maxn<<1];
inline int read(){
    char ch=getchar(); int sum=0; int f=1;
    while(!isdigit(ch)) f=ch=='-'?-1:1,ch=getchar();
    while(isdigit(ch)) sum=sum*10+ch-'0',ch=getchar();
    return sum*f; 
}
inline void FFT(Complex *a,int len,int on){
    for(int i=0;i<len;i++)
        if(i<r[i]) swap(a[i],a[r[i]]);
    for(int i=2;i<=len;i<<=1){//一次合并的长度
        Complex wn(double(cos(2*Pi/i)),double(sin(on*2*Pi/i)));//当前单位根
        for(int j=0;j<len;j+=i){//对每一块分别计算
            Complex w(1,0);
            for(int k=j;k<j+i/2;k++){//蝴蝶操作,计算当前多项式的点值
                Complex u=a[k];
                Complex t=w*a[k+i/2];
                a[k]=u+t;
                a[k+i/2]=u-t;
                w=wn*w;
            }
        }
    }
    if(on==-1) for(int i=0;i<len;i++) a[i].x/=len; 
}
int main(){
    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();
    len=1; int cnt=0;
    while(len<=n+m) len<<=1,cnt++;//补成2^k长度
    for(int i=0;i<len;i++) r[i]=(r[i>>1]>>1)|((i&1)<<(cnt-1));//找到对应位置
    FFT(a,len,1);//DFT
    FFT(b,len,1);
    for(int i=0;i<len;i++) a[i]=a[i]*b[i];//多项式乘法(用点值计算)
    FFT(a,len,-1);//IDFT
    for(int i=0;i<=n+m;i++) printf("%d ",int(a[i].x+0.5));//注意加0.5,防止被卡精度
    printf("\n");
    return 0;
} 

我们这是“多项式入门”,也就入门到这了!

多项式还有很多内容,包括:

  • 快速数论变换(NTT)
  • 快速沃尔什变换(FWT)
  • 多项式求逆
  • 多项式开方
  • 多项式除法
  • 多项式取模
  • 多项式的对数函数与指数函数
  • 多项式的三角函数与反三角函数
  • 多项式牛顿迭代
  • 多项式快速插值|多点求值

这些已经超过我的能力范围(我毕竟还只是一个初中的蒟蒻OIer)。

如果以后有机会的话,我会更新一个叫做多项式进阶的博客的!

如有不足之处请指正,谢谢!

参考资料:

1.苏科版七年级上册《数学》教科书

2.2002年国家集训队论文 张家琳《多项式乘法》

3.2016年国家集训队论文 毛啸 《再探快速傅里叶变换》

3.华东师范大学出版社《奥数教程》高中第三分册

4.百度百科 范德蒙行列式

5.OI wiki 快速傅里叶变换

转载于:https://www.cnblogs.com/ybwowen/p/11153817.html

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值