本来很久以前就学了,但是我一直没有写博客,直到最近要用的时候才发现忘了。于是就有了这篇博客.
我们知道,如果直接暴力将两个多项式比如
A
(
x
)
=
a
0
+
a
1
x
+
a
2
x
2
+
.
.
.
+
a
n
x
n
A(x)=a_0+a_1x+a_2x^2+...+a_nx^n
A(x)=a0+a1x+a2x2+...+anxn
B
(
x
)
=
b
0
+
b
1
x
+
b
2
x
2
+
.
.
.
+
b
n
x
n
B(x)=b_0+b_1x+b_2x^2+...+b_nx^n
B(x)=b0+b1x+b2x2+...+bnxn
相乘将会花费
O
(
n
2
)
O(n^2)
O(n2)的时间,有没有更快的方法呢?
我们注意到,耗时的原因是每一项都要花费
O
(
n
)
O(n)
O(n)的时间来乘,合起来乘的难度较大,说不定换一种表示方法就可以不一样了。
我们要确定一个有
n
n
n次项的方程,不仅可以用上面的方法,我们也可以用
n
n
n个点(向量)来表示,而且这样一来方程就可以唯一确定了。
很明显如果是用
n
n
n个点来表示
A
(
x
)
A(x)
A(x)的话,
A
(
x
)
,
B
(
x
)
A(x),B(x)
A(x),B(x)可以表示为:
(
x
1
,
A
(
x
1
)
)
,
(
x
2
,
A
(
x
2
)
)
,
.
.
.
,
(
x
n
,
A
(
x
n
)
)
(x_1,A(x_1)),(x_2,A(x_2)),...,(x_n,A(x_n))
(x1,A(x1)),(x2,A(x2)),...,(xn,A(xn))
(
x
1
,
B
(
x
1
)
)
,
(
x
2
,
B
(
x
2
)
)
,
.
.
.
,
(
x
n
,
B
(
x
n
)
)
(x_1,B(x_1)),(x_2,B(x_2)),...,(x_n,B(x_n))
(x1,B(x1)),(x2,B(x2)),...,(xn,B(xn))
由于我们要求的
C
(
x
)
=
A
(
x
)
∗
B
(
x
)
C(x)=A(x)*B(x)
C(x)=A(x)∗B(x)因此
C
(
x
)
C(x)
C(x)可以表示为:
(
x
1
,
A
(
x
1
)
∗
B
(
x
1
)
)
,
(
x
2
,
A
(
x
2
)
∗
B
(
x
2
)
)
,
.
.
.
,
(
x
n
,
A
(
x
n
)
∗
B
(
x
n
)
)
(x_1,A(x_1)*B(x_1)),(x_2,A(x_2)*B(x_2)),...,(x_n,A(x_n)*B(x_n))
(x1,A(x1)∗B(x1)),(x2,A(x2)∗B(x2)),...,(xn,A(xn)∗B(xn))
在这种表示方法下我们只需要
O
(
n
)
O(n)
O(n)时间就可以将两个方程相乘,但是我们已有的是
A
(
x
)
A(x)
A(x)和
B
(
x
)
B(x)
B(x)的系数表示法的式子。
但如果暴力转成点值表示法,需要
O
(
n
2
)
O(n^2)
O(n2)的时间,时间复杂度并没有丝毫的减少,而且常数反而变大了。我们需要找到更快的能在系数表示法和点值表示法之间转换的方法。
很明显,我们在带入n个值求点是在做操作重复的运算,而且由于这n个值的取值我们现在还没有确定,这里应该可以通过巧妙的取值来达到减少运算的目的,要是我们选的数的次方之间可以相互轮回,那么应该可以简化运算。一维的实数相乘的话只能在数轴上面走,二维的虚数就比较方便了,因为
(
c
o
s
(
a
)
+
i
s
i
n
(
a
)
)
n
=
(
c
o
s
(
n
a
)
+
i
s
i
n
(
n
a
)
)
(cos(a)+isin(a))^n=(cos(na)+isin(na))
(cos(a)+isin(a))n=(cos(na)+isin(na))我们如果用这样的数,那么就它的n次方就是相当于在平面上转圈,如下图:
因此如上图
(
w
8
1
)
n
=
w
8
n
(w_8^1)^n=w_8^n
(w81)n=w8n.
我们可以发现它有这些性质:
w
2
n
2
m
=
w
n
m
w_{2n}^{2m}=w_n^m
w2n2m=wnm
w
n
m
+
n
2
=
−
w
n
m
w_n^{m+\frac{n}{2}}=-w_n^m
wnm+2n=−wnm
因此各次方的关系就有了,我们可以把最后的式子排在一起看看
比如现在只有4个:
a
0
+
a
1
∗
w
4
0
+
a
2
∗
(
w
4
0
)
2
+
a
3
∗
(
w
4
0
)
3
a_0+a_1*w_4^0+a_2*(w_4^0)^2+a_3*(w_4^0)^3
a0+a1∗w40+a2∗(w40)2+a3∗(w40)3
a
0
+
a
1
∗
w
4
1
+
a
2
∗
(
w
4
1
)
2
+
a
3
∗
(
w
4
1
)
3
a_0+a_1*w_4^1+a_2*(w_4^1)^2+a_3*(w_4^1)^3
a0+a1∗w41+a2∗(w41)2+a3∗(w41)3
a
0
+
a
1
∗
w
4
2
+
a
2
∗
(
w
4
2
)
2
+
a
3
∗
(
w
4
2
)
3
a_0+a_1*w_4^2+a_2*(w_4^2)^2+a_3*(w_4^2)^3
a0+a1∗w42+a2∗(w42)2+a3∗(w42)3
a
0
+
a
1
∗
w
4
3
+
a
2
∗
(
w
4
3
)
2
+
a
3
∗
(
w
4
3
)
3
a_0+a_1*w_4^3+a_2*(w_4^3)^2+a_3*(w_4^3)^3
a0+a1∗w43+a2∗(w43)2+a3∗(w43)3
即:
a
0
+
a
1
∗
w
4
0
+
a
2
∗
w
4
0
+
a
3
∗
w
4
0
a_0+a_1*w_4^0+a_2*w_4^0+a_3*w_4^0
a0+a1∗w40+a2∗w40+a3∗w40
a
0
+
a
1
∗
w
4
1
+
a
2
∗
w
4
2
+
a
3
∗
w
4
3
a_0+a_1*w_4^1+a_2*w_4^2+a_3*w_4^3
a0+a1∗w41+a2∗w42+a3∗w43
a
0
+
a
1
∗
w
4
2
+
a
2
∗
w
4
4
+
a
3
∗
w
4
6
a_0+a_1*w_4^2+a_2*w_4^4+a_3*w_4^6
a0+a1∗w42+a2∗w44+a3∗w46
a
0
+
a
1
∗
w
4
3
+
a
2
∗
w
4
6
+
a
3
∗
w
4
9
a_0+a_1*w_4^3+a_2*w_4^6+a_3*w_4^9
a0+a1∗w43+a2∗w46+a3∗w49
由于
w
n
m
+
n
=
w
n
m
w_n^{m+n}=w_n^m
wnm+n=wnm,我们可以把次数模n加强联系即:
a
0
+
a
1
∗
w
4
0
+
a
2
∗
w
4
0
+
a
3
∗
w
4
0
a_0+a_1*w_4^0+a_2*w_4^0+a_3*w_4^0
a0+a1∗w40+a2∗w40+a3∗w40
a
0
+
a
1
∗
w
4
1
+
a
2
∗
w
4
2
+
a
3
∗
w
4
3
a_0+a_1*w_4^1+a_2*w_4^2+a_3*w_4^3
a0+a1∗w41+a2∗w42+a3∗w43
a
0
+
a
1
∗
w
4
2
+
a
2
∗
w
4
0
+
a
3
∗
w
4
2
a_0+a_1*w_4^2+a_2*w_4^0+a_3*w_4^2
a0+a1∗w42+a2∗w40+a3∗w42
a
0
+
a
1
∗
w
4
3
+
a
2
∗
w
4
2
+
a
3
∗
w
4
1
a_0+a_1*w_4^3+a_2*w_4^2+a_3*w_4^1
a0+a1∗w43+a2∗w42+a3∗w41
由于
w
4
2
=
−
w
4
0
,
w
4
3
=
−
w
4
1
w_4^2=-w_4^0,w_4^3=-w_4^1
w42=−w40,w43=−w41我们可以进一步把次数降低加强联系即:
a
0
+
a
1
∗
w
4
0
+
a
2
∗
w
4
0
+
a
3
∗
w
4
0
a_0+a_1*w_4^0+a_2*w_4^0+a_3*w_4^0
a0+a1∗w40+a2∗w40+a3∗w40
a
0
+
a
1
∗
w
4
1
−
a
2
∗
w
4
0
−
a
3
∗
w
4
1
a_0+a_1*w_4^1-a_2*w_4^0-a_3*w_4^1
a0+a1∗w41−a2∗w40−a3∗w41
a
0
−
a
1
∗
w
4
0
+
a
2
∗
w
4
0
−
a
3
∗
w
4
0
a_0-a_1*w_4^0+a_2*w_4^0-a_3*w_4^0
a0−a1∗w40+a2∗w40−a3∗w40
a
0
−
a
1
∗
w
4
1
−
a
2
∗
w
4
0
+
a
3
∗
w
4
1
a_0-a_1*w_4^1-a_2*w_4^0+a_3*w_4^1
a0−a1∗w41−a2∗w40+a3∗w41
可以发现其中的(0,2)(1,3)式的次数是完全一样的,系数的符号有一半一样,另一半互为相反数,其实这很好证明第
m
m
m式和第
m
+
n
2
m+\frac {n}{2}
m+2n式的关系。
f
m
=
a
0
+
a
1
∗
w
n
m
+
a
2
∗
w
n
2
m
+
.
.
.
+
a
n
−
1
∗
w
n
(
n
−
1
)
∗
m
f_m=a_0+a_1*w_n^m+a_2*w_n^{2m}+...+a_{n-1}*w_n^{(n-1)*m}
fm=a0+a1∗wnm+a2∗wn2m+...+an−1∗wn(n−1)∗m
f
m
+
n
2
=
a
0
+
a
1
∗
w
n
m
+
n
2
+
a
2
∗
w
n
2
m
+
.
.
.
+
a
n
−
1
∗
w
n
(
n
−
1
)
∗
(
m
+
n
2
)
f_{m+\frac{n}{2}}=a_0+a_1*w_n^{m+\frac{n}{2}}+a_2*w_n^{2m}+...+a_{n-1}*w_n^{(n-1)*(m+\frac{n}{2})}
fm+2n=a0+a1∗wnm+2n+a2∗wn2m+...+an−1∗wn(n−1)∗(m+2n)
即
f
m
+
n
2
=
a
0
−
a
1
∗
w
n
m
+
a
2
∗
w
n
2
m
+
.
.
.
−
a
n
−
1
∗
w
n
(
n
−
1
)
∗
m
f_{m+\frac{n}{2}}=a_0-a_1*w_n^m+a_2*w_n^{2m}+...-a_{n-1}*w_n^{(n-1)*m}
fm+2n=a0−a1∗wnm+a2∗wn2m+...−an−1∗wn(n−1)∗m(假设n是偶数)
那么我们可以把这之间相同的部分拿出来
令
A
0
=
a
0
+
a
2
∗
w
n
2
m
+
a
4
∗
w
n
4
m
+
.
.
.
+
a
n
−
2
∗
w
n
(
n
−
2
)
∗
m
A_0=a_0+a_2*w_n^{2m}+a_4*w_n^{4m}+...+a_{n-2}*w_n^{(n-2)*m}
A0=a0+a2∗wn2m+a4∗wn4m+...+an−2∗wn(n−2)∗m
A
1
=
a
1
+
a
3
∗
w
n
2
m
+
a
5
∗
w
n
4
m
+
.
.
.
+
a
n
−
1
∗
w
n
(
n
−
2
)
∗
m
A_1=a_1+a_3*w_n^{2m}+a_5*w_n^{4m}+...+a_{n-1}*w_n^{(n-2)*m}
A1=a1+a3∗wn2m+a5∗wn4m+...+an−1∗wn(n−2)∗m
则
A
0
=
a
0
+
a
2
∗
w
n
2
m
+
a
4
∗
w
n
2
2
m
+
.
.
.
+
a
n
−
2
∗
w
n
2
(
n
−
2
)
∗
m
/
2
A_0=a_0+a_2*w_{\frac{n}{2}}^{m}+a_4*w_{\frac{n}{2}}^{2m}+...+a_{n-2}*w_{\frac{n}{2}}^{(n-2)*m/2}
A0=a0+a2∗w2nm+a4∗w2n2m+...+an−2∗w2n(n−2)∗m/2
A
1
=
a
1
+
a
3
∗
w
n
2
m
+
a
5
∗
w
n
2
2
m
+
.
.
.
+
a
n
−
1
∗
w
n
2
(
n
−
2
)
∗
m
/
2
A_1=a_1+a_3*w_{\frac{n}{2}}^{m}+a_5*w_{\frac{n}{2}}^{2m}+...+a_{n-1}*w_{\frac{n}{2}}^{(n-2)*m/2}
A1=a1+a3∗w2nm+a5∗w2n2m+...+an−1∗w2n(n−2)∗m/2
那么很明显
f
m
=
A
0
+
A
1
∗
w
n
m
f_m=A_0+A_1*w_n^m
fm=A0+A1∗wnm
f
m
+
n
2
=
A
0
+
A
1
∗
w
n
m
+
n
2
=
A
0
−
A
1
∗
w
n
m
f_{m+\frac{n}{2}}=A_0+A_1*w_n^{m+\frac{n}{2}}=A_0-A_1*w_n^m
fm+2n=A0+A1∗wnm+2n=A0−A1∗wnm
因此我们只要把
f
m
f_m
fm和
f
m
+
n
2
f_{m+\frac{n}{2}}
fm+2n求出来就可以一下求出来
A
0
A_0
A0和
A
1
A_1
A1 而且
f
f
f的数据范围只有
A
A
A的一半。也就是说只要
l
o
g
2
(
n
)
log_2(n)
log2(n)层运算就可以求出解了,说以总的时间复杂度只有
n
l
o
g
2
(
n
)
nlog_2(n)
nlog2(n).
a 0 + a 1 w 4 0 + a 2 w 4 0 + a 3 w 4 0 a_0+a_1w_4^0+a_2w_4^0+a_3w_4^0 a0+a1w40+a2w40+a3w40 | a 0 + a 1 w 4 1 + a 2 w 4 2 + a 3 w 4 3 a_0+a_1w_4^1+a_2w_4^2+a_3w_4^3 a0+a1w41+a2w42+a3w43 | a 0 + a 1 w 4 2 + a 2 w 4 4 + a 3 w 4 6 a_0+a_1w_4^2+a_2w_4^4+a_3w_4^6 a0+a1w42+a2w44+a3w46 | a 0 + a 1 w 4 3 + a 2 w 4 6 + a 3 w 4 9 a_0+a_1w_4^3+a_2w_4^6+a_3w_4^9 a0+a1w43+a2w46+a3w49 |
---|---|---|---|
a 0 + a 2 w 2 0 a_0+a_2w_2^0 a0+a2w20 | a 0 + a 2 w 2 1 a_0+a_2w_2^1 a0+a2w21 | a 1 + a 3 w 2 0 a_1+a_3w_2^0 a1+a3w20 | a 1 + a 3 w 2 1 a_1+a_3w_2^1 a1+a3w21 |
a 0 a_0 a0 | a 2 a_2 a2 | a 1 a_1 a1 | a 3 a_3 a3 |
为了方便处理我们将对应位置的f对着对应位置的A,表示成数字就是:
0 1 2 3 4 5 6 7
0 2 4 6 1 3 5 7
0 4 2 6 1 5 3 7
不难发现之后得到的位置的二进制就是按反过来之后排序,因为我们第一次把&1=0的移动到了前面,而吧&1=1的移到了后面,所以之后第二次把&2=0的移动到了前面,而吧&2=1的移到了后面,之后依次这样就可以了。这就是蝴蝶变换。
因此我们在实际操作的时候就反着来,从下至上依次求出每一层,这里不递归的原因是因为递归太慢了。
说以我们现在可以用
O
(
n
l
o
g
2
(
n
)
)
O(nlog_2(n))
O(nlog2(n))的时间复杂度正着把系数表示法变成点值表示法,那么怎么倒着装呢?
很明显为了求出上面的值,我们采取的方法是倒着来,既然
f
m
=
A
0
+
A
1
∗
w
n
m
f_m=A_0+A_1*w_n^m
fm=A0+A1∗wnm
f
m
+
n
2
=
A
0
−
A
1
∗
w
n
m
f_{m+\frac{n}{2}}=A_0-A_1*w_n^m
fm+2n=A0−A1∗wnm
那么
2
∗
A
0
=
f
m
+
f
m
+
n
2
2*A_0=f_m+f_{m+\frac{n}{2}}
2∗A0=fm+fm+2n
2
∗
A
1
=
(
f
m
−
f
m
+
n
2
)
∗
w
n
−
m
2*A_1=(f_m-f_{m+\frac{n}{2}})*w_n^{-m}
2∗A1=(fm−fm+2n)∗wn−m
我们先暴力把最终得到的式子求出来再统一除
n
(
n
=
2
某
次
方
)
n(n=2^{某次方})
n(n=2某次方),得到:
4 ∗ a 0 4*a_0 4∗a0 | 4 ∗ a 1 4*a_1 4∗a1 | 4 ∗ a 2 4*a_2 4∗a2 | 4 ∗ a 3 4*a_3 4∗a3 |
---|---|---|---|
2 ∗ ( a 0 + a 2 w 4 0 ) 2*(a_0+a_2w_4^0) 2∗(a0+a2w40) | 2 ∗ ( a 1 + a 3 w 4 0 ) 2*(a_1+a_3w_4^0) 2∗(a1+a3w40) | 2 ∗ ( a 0 + a 2 w 4 2 ) 2*(a_0+a_2w_4^2) 2∗(a0+a2w42) | 2 ∗ ( a 1 + a 3 w 4 2 ) 2*(a_1+a_3w_4^2) 2∗(a1+a3w42) |
a 0 + a 1 w 4 0 + a 2 w 4 0 + a 3 w 4 0 a_0+a_1w_4^0+a_2w_4^0+a_3w_4^0 a0+a1w40+a2w40+a3w40 | a 0 + a 1 w 4 2 + a 2 w 4 4 + a 3 w 4 6 a_0+a_1w_4^2+a_2w_4^4+a_3w_4^6 a0+a1w42+a2w44+a3w46 | a 0 + a 1 w 4 1 + a 2 w 4 2 + a 3 w 4 3 a_0+a_1w_4^1+a_2w_4^2+a_3w_4^3 a0+a1w41+a2w42+a3w43 | a 0 + a 1 w 4 3 + a 2 w 4 6 + a 3 w 4 9 a_0+a_1w_4^3+a_2w_4^6+a_3w_4^9 a0+a1w43+a2w46+a3w49 |
比如这里n就是4,FFT只能解决n是二的整次幂的情况,如果开始的n不是2的整次幂那么加一堆系数为0的项就可以了,反正加的项又不到一倍,时间也多不了多少。
这样式子就可以反过来了
不过貌似再写逆变换有点麻烦,大佬们搞了个逆矩阵至今没搞懂,留坑了。
这里是代码(洛谷3803)
#include<cstdio>
#include<complex>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=4000005;
typedef complex<double> cpx;
const long double PI=acos(0.0)*2.0;
int N,M;
void FFT(cpx a[],int n,double op)
{
for(int i=1,j=0;i<n-1;i++)
{
for(int s=n;j^=(s>>=1),(~j)&s;);
if(i<j)
swap(a[i],a[j]);
}
for(int len=2,mid=1;len<=n;len<<=1,mid<<=1)
{
cpx wm(cos(2.0*PI/len),op*sin(2.0*PI/len));
for(int now=0;now<n;now+=len)
{
cpx w(1,0);
for(int k=now;k<now+mid;k++,w=w*wm)
{
cpx l=a[k],r=w*a[k+mid];
a[k]=l+r,a[k+mid]=l-r;
}
}
}
if(op==-1)
{
for(int i=0;i<n;i++)
a[i]/=n;
}
}
cpx A[MAXN],B[MAXN];
void mul(int a[],int n,int b[],int m,int c[])
{
//memset(A,0,sizeof(A));
//memset(B,0,sizeof(B));
for(int i=0;i<n;i++)
A[i]=cpx(a[i],0);
for(int i=0;i<m;i++)
B[i]=cpx(b[i],0);
int l=n+m-1,len=1;
while(len<l)
len<<=1;
FFT(A,len,1);
FFT(B,len,1);
for(int i=0;i<len;i++)
A[i]=A[i]*B[i];
FFT(A,len,-1);
for(int i=0;i<len;i++)
c[i]=int(A[i].real()+0.5);
}
int F[MAXN],G[MAXN],an[MAXN];
int main()
{
scanf("%d %d",&N,&M);
N++,M++;
for(int i=0;i<N;i++)
scanf("%d",&F[i]);
for(int i=0;i<M;i++)
scanf("%d",&G[i]);
mul(F,N,G,M,an);
for(int i=0;i<N+M-1;i++)
printf("%d%c",an[i],i==N+M-2?'\n':' ');
}