简单的预备知识
系数表达
用多项式每一项的系数表示多项式
点值表达
用 个横坐标各不相同的点 来表示一个次数界为 的多项式
求值
系数表达
点值表达
霍纳法则
插值
点值表达
系数表达
拉格朗日公式
系数表达式求多项式加,乘法
点值表达式求多项式加,乘法
显然的,用系数表达式算多项式乘法简直是龟速
而使用点值表达式算多项式乘法则快很多
可以看出主要若使用点值表达式,主要复杂度在求值和插值(均为
)
显然如果求值插值不能优化的话其复杂度甚至是不如系数乘法的(常数大)
所以我们可以利用一些特殊的方式来加速
引入单位复数根
次单位复数根是满足
的复数
,
次单位复数根恰有
个
对于
,这些根是
.(
)
用复数的指数形式的定义
可以建立一个以实数为
轴,以虚数为
轴的坐标系
然后这些单位复数根在坐标轴上正好是半径为1的圆上的点
我们将
称为主
次单位根,利用它,其他的单位复数根都是其幂次
然后这些单位复数根的乘法就相当于角度的转换
单位复数根之间满足乘法群的性质
消去引理:
* 证明:
推论:
折半引理: 若
为偶数,则
次单位复数根的平方的集合就是
次单位复数根的集合,特别的,每个
次单位复数根出现两次
* 证明:
[消去引理]
正式开始
对于
次多项式,我们希望取得其在
处的值
相当于求这样一个矩阵:
其中 为取到 时的点值, 为第 位的系数
现定义:
易得:
因为当 和 时,两数平方相等
所以说我们的取值集合就缩小了一半
这样一直递归下去
每层有 个块,每个块有 个取值,所以一共只需计算 次,共 层,总时间复杂度为
非常的
这个过程也叫做
得到点值表达式以后我们就只需要 的点值乘法,这里不再赘述
那么接下来的任务就是插值了,也就是逆
易得这个矩阵:
其中 为取到 时的点值, 为第 位的系数
所以
单位矩阵
所以 中位于第 行第 列(均从0开始标号)的元素为
所以
是不是和原来的公式很像?
只不过 的系数变成了负的
多了一个 而已
发现也可以 解决
这样就做完啦!
但是具体代码中还是有丶东西
看注释吧!
代码如下:
#include <bits/stdc++.h>
using namespace std;
const int N =100010;
const double pi=acos(-1);
struct Complex {
double x;
double y;
Complex() {};
Complex(double _x,double _y) { x=_x;y=_y; }
Complex operator + (const Complex o) { return Complex(x+o.x,y+o.y); }
Complex operator - (const Complex o) { return Complex(x-o.x,y-o.y); }
Complex operator * (const Complex o) { return Complex(x*o.x-y*o.y,x*o.y+y*o.x); }
}a[N<<2],b[N<<2];
int r[N<<2];
int n,m,nn;
void fft(Complex *now,int f) {
for(int i=0;i<n;++i)//按块将其分到一起
if(i<r[i]) swap(now[i],now[r[i]]);
for(int i=1;i<n;i<<=1) {//枚举合并的块的大小
Complex wn(cos(pi/i),f*sin(pi/i));//单位元,若进行逆DFT变换,则反向旋转
for(int j=0;j<n;j+=(i<<1)) {//枚举头
Complex w(1,0);
for(int k=0;k<i;++k,w=w*wn) {//合并,蝴蝶变换
Complex x=now[j+k],y=w*now[j+k+i];
now[j+k]=x+y;
now[j+k+i]=x-y;
}
}
}
//神奇的是蝴蝶变换完了以后他又恢复正常顺序了,我也不知道为什么
if(f==-1)
for(int i=0;i<n;++i)
now[i].x/=n;
}
int main() {
scanf("%d%d",&n,&m);
for(int i=0;i<=n;++i) scanf("%lf",&a[i].x);
for(int i=0;i<=m;++i) scanf("%lf",&b[i].x);
m+=n;
for(n=1;n<=m;n<<=1) nn++;
for(int i=0;i<n;++i) {r[i]=(r[i>>1]>>1)|((i&1)<<(nn-1));}
fft(a,1);fft(b,1);
for(int i=0;i<=n;++i) { a[i]=a[i]*b[i]; }
fft(a,-1);//逆DFT
for(int i=0;i<=m;++i)
printf("%d ",(int)(a[i].x+0.5))//防止精度出锅;
return 0;
}