请各位看官移至 此处
简单的预备知识
系数表达
用多项式每一项的系数表示多项式
点值表达
用 n n 个横坐标各不相同的点来表示一个次数界为 n n 的多项式
求值
系数表达点值表达
O(n2)
O
(
n
2
)
霍纳法则
插值
点值表达
=>
=>
系数表达
O(n2)
O
(
n
2
)
拉格朗日公式
系数表达式求多项式加,乘法
A={a0,a1,⋯,an−1}
A
=
{
a
0
,
a
1
,
⋯
,
a
n
−
1
}
B={a′0,a′1,⋯,a′n−1}
B
=
{
a
0
′
,
a
1
′
,
⋯
,
a
n
−
1
′
}
C=A+B={a0+a′0,a1+a′1,⋯an−1+a′n−1}
C
=
A
+
B
=
{
a
0
+
a
0
′
,
a
1
+
a
1
′
,
⋯
a
n
−
1
+
a
n
−
1
′
}
C=A∗B,cx=∑xi=0ai∗a′x−i
C
=
A
∗
B
,
c
x
=
∑
i
=
0
x
a
i
∗
a
x
−
i
′
点值表达式求多项式加,乘法
A={(x0,y0),(x1,y1),⋯,(xn−1,yn−1)}
A
=
{
(
x
0
,
y
0
)
,
(
x
1
,
y
1
)
,
⋯
,
(
x
n
−
1
,
y
n
−
1
)
}
B={(x0,y′0),(x1,y′1),⋯,(xn−1,y′n−1)}
B
=
{
(
x
0
,
y
0
′
)
,
(
x
1
,
y
1
′
)
,
⋯
,
(
x
n
−
1
,
y
n
−
1
′
)
}
C=A+B={(x0,y0+y′0),(x1,y1+y′1),⋯,(xn−1,yn−1+y′n−1)}
C
=
A
+
B
=
{
(
x
0
,
y
0
+
y
0
′
)
,
(
x
1
,
y
1
+
y
1
′
)
,
⋯
,
(
x
n
−
1
,
y
n
−
1
+
y
n
−
1
′
)
}
C=A∗B={(x0,y0∗y′0),(x1,y1∗y′1),⋯,(xn−1,yn−1∗y′n−1)}
C
=
A
∗
B
=
{
(
x
0
,
y
0
∗
y
0
′
)
,
(
x
1
,
y
1
∗
y
1
′
)
,
⋯
,
(
x
n
−
1
,
y
n
−
1
∗
y
n
−
1
′
)
}
显然的,用系数表达式算多项式乘法简直是龟速
O(n2)
O
(
n
2
)
而使用点值表达式算多项式乘法则快很多
(O(n))
(
O
(
n
)
)
可以看出主要若使用点值表达式,主要复杂度在求值和插值(均为
O(n2)
O
(
n
2
)
)
显然如果求值插值不能优化的话其复杂度甚至是不如系数乘法的(常数大)
所以我们可以利用一些特殊的方式来加速
引入单位复数根
n
n
次单位复数根是满足的复数
w
w
,次单位复数根恰有
n
n
个
对于,这些根是
e2πikn
e
2
π
i
k
n
.(
eπi=−1,e2πi=1
e
π
i
=
−
1
,
e
2
π
i
=
1
)
用复数的指数形式的定义
eui=cos(u)+i∗sin(u)
e
u
i
=
c
o
s
(
u
)
+
i
∗
s
i
n
(
u
)
可以建立一个以实数为
x
x
轴,以虚数为轴的坐标系
然后这些单位复数根在坐标轴上正好是半径为1的圆上的点
我们将
e2πi1n
e
2
π
i
1
n
称为主
n
n
次单位根,利用它,其他的单位复数根都是其幂次
然后这些单位复数根的乘法就相当于角度的转换
单位复数根之间满足乘法群的性质
消去引理:
wdkdn=wkn
w
d
n
d
k
=
w
n
k
* 证明:
wdkdn=(e2πi1dn)dk=(e2πi1n)k=wkn
w
d
n
d
k
=
(
e
2
π
i
1
d
n
)
d
k
=
(
e
2
π
i
1
n
)
k
=
w
n
k
推论: wn2n=−1 w 2 n n = − 1
折半引理: 若
n
n
为偶数,则次单位复数根的平方的集合就是
n2
n
2
次单位复数根的集合,特别的,每个
n2
n
2
次单位复数根出现两次
* 证明:
(wkn)2=wkn2,k∈[0,n−1]
(
w
n
k
)
2
=
w
n
2
k
,
k
∈
[
0
,
n
−
1
]
[消去引理]
正式开始
对于
n
n
次多项式,我们希望取得其在处的值
相当于求这样一个矩阵:
其中 Ai A i 为取到 win w n i 时的点值, ai a i 为第 i i 位的系数
现定义:
A[0](x)=a0+a2x+⋯+an−2xn−22 A [ 0 ] ( x ) = a 0 + a 2 x + ⋯ + a n − 2 x n − 2 2
A[1](x)=a1+a3x+⋯+an−1xn−22 A [ 1 ] ( x ) = a 1 + a 3 x + ⋯ + a n − 1 x n − 2 2
易得:
因为当 x=wkn x = w n k 和 x=wk+n2n x = w n k + n 2 时,两数平方相等
所以说我们的取值集合就缩小了一半
这样一直递归下去
每层有 2d 2 d 个块,每个块有 n2d n 2 d 个取值,所以一共只需计算 n n 次,共层,总时间复杂度为 O(nlog(n)) O ( n l o g ( n ) )
非常的 nice n i c e
这个过程也叫做 DFT D F T
得到点值表达式以后我们就只需要 O(n) O ( n ) 的点值乘法,这里不再赘述
那么接下来的任务就是插值了,也就是逆
DFT
D
F
T
易得这个矩阵:
其中 Ci C i 为取到 win w n i 时的点值, ci c i 为第 i i 位的系数
所以
V−1∗V= V − 1 ∗ V = 单位矩阵
所以 V−1 V − 1 中位于第 i i 行第列(均从0开始标号)的元素为 w−ijnn w n − i j n
所以
是不是和原来的公式很像?
只不过 w w 的系数变成了负的
多了一个而已
发现也可以 DFT D F T 解决
这样就做完啦!
但是具体代码中还是有丶东西
看注释吧!
代码如下:
#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;
}