问题
给出长度为
n
n
n的多项式
f
(
x
)
=
∑
i
=
0
n
?
1
a
i
x
i
f(x)=\sum_{i=0}^{n-1}a_{i}x^{i}
f(x)=∑i=0n?1?ai?xi和长度为
m
m
m多项式
g
(
x
)
=
∑
i
=
0
m
?
1
b
i
x
i
g(x)=\sum_{i=0}^{m-1}b_{i}x^{i}
g(x)=∑i=0m?1?bi?xi。求解多项式
h
(
x
)
=
f
(
x
)
×
g
(
x
)
h(x)=f(x)\times g(x)
h(x)=f(x)×g(x)。
(
1
≤
n
≤
1
0
5
)
(1\leq n\leq 10^{5})
(1≤n≤105)
系数表示法和点值表示法
多项式的两种表示方法,系数表示法和点值表示法。 系数表示法:我们存储
f
(
x
)
f(x)
f(x)的每一项
x
i
x^{i}
xi的系数
a
i
a_{i}
ai?。 点值表示法:由大家都知道知,
N
N
N个点可以确定一个最高次为
N
?
1
N-1
N?1多项式,所以我们可以存储多项式上任意
N
N
N个点,来确定这个多项式。
点值表示法有个性质是,对于多项式
f
f
f的一个点值
(
x
,
y
1
)
(x,y_{1})
(x,y1?),和多项式
g
g
g的一个点值
(
x
,
y
2
)
(x,y_{2})
(x,y2?),可以得到两个多项式相乘的多项式
h
h
h在横坐标为
x
x
x处的点为
(
x
,
y
1
×
y
2
)
(x,y_{1}\times y_{2})
(x,y1?×y2?)。
所以在对于长度为
n
n
n的多项式
f
f
f和长度为
m
m
m的多项式
g
g
g做乘法时,我们可以算出
f
f
f的
n
+
m
?
1
n+m-1
n+m?1个点值和
g
g
g的
n
+
m
?
1
n+m-1
n+m?1个点值,且要求两个多项式点值所取的
x
x
x相同。 我们就可以通过遍历算出这
n
+
m
?
1
n+m-1
n+m?1个横坐标的点值纵坐标相乘,来得到相乘后多项式的
n
+
m
?
1
n+m-1
n+m?1个点值,也就是新多项式的点值表示法。 (注意:虽然我们存储
f
f
f只需要
n
n
n个点值,但是为了计算出长度为
n
+
m
?
1
n+m-1
n+m?1的
h
h
h,我们仍要先得到
f
f
f的(n+m-1)个点值)
因此,假如我们可以通过
o
(
n
l
o
g
n
)
o(nlogn)
o(nlogn)的办法,将系数表示法转化为点值表示法,然后将两个多项式点值
o
(
n
)
o(n)
o(n)相乘,最后通过
o
(
n
l
o
g
n
)
o(nlogn)
o(nlogn)的办法将点值表示法转化回系数表示法,就可以解决这个问题了。
后面来介绍如何将系数表示发和点值表示法进行转换。
离散傅里叶变换(DFT)
对于没有周期的函数,我们可以认为其周期为
∞
\infty
∞,用无穷个周期函数去拟合。 同理,对于一个长度为
N
N
N的离散多项式
x
(
n
)
(
n
=
0
,
1
,
2..
N
?
1
)
x(n) (n=0,1,2..N-1)
x(n)(n=0,1,2..N?1),可以通过
N
N
N个周期函数来拟合。(
N
N
N个周期函数只能拟合
N
N
N个点,不能用来拟合连续多项式函数) 由此我们得到离散傅里叶级数展开式如下。
X
(
k
)
=
∑
n
=
0
N
?
1
x
(
n
)
e
?
2
π
i
k
n
N
X(k)=\sum_{n=0}^{N-1}x(n)e^{-2\pi ik\frac{n}{N}}
X(k)=n=0∑N?1?x(n)e?2πikNn? 其中
e
?
2
π
i
k
n
N
=
c
o
s
(
?
2
π
k
n
N
)
+
i
s
i
n
(
?
2
π
k
n
N
)
e^{-2\pi ik\frac{n}{N}}=cos(-2\pi k\frac{n}{N})+isin(-2\pi k\frac{n}{N})
e?2πikNn?=cos(?2πkNn?)+isin(?2πkNn?)是关于
n
n
n的周期为
N
N
N的函数。(由欧拉公式
e
i
x
=
c
o
s
x
+
i
s
i
n
x
e^{ix}=cos x+isin x
eix=cosx+isinx得)
离散傅里叶逆变换(IDFT)
对于变换后得到的
X
(
n
)
X(n)
X(n),我们可以通过离散傅里叶逆变换公式得到原函数
x
(
n
)
x(n)
x(n)。与
D
F
T
DFT
DFT公式差不多,在原来的基础上
i
i
i变成了
?
i
-i
?i,前面乘上一个
1
N
\frac{1}{N}
N1?。公式如下。
x
(
n
)
=
1
N
∑
k
=
0
N
?
1
X
(
k
)
e
2
π
i
k
n
N
x(n)=\frac{1}{N}\sum_{k=0}^{N-1}X(k)e^{2\pi ik\frac{n}{N}}
x(n)=N1?k=0∑N?1?X(k)e2πikNn?
快速傅里叶变换(FFT)
由前面讲过的,我们要解决的核心问题就是,算出多项式的
N
N
N个点值。 大致做法是,我们求出函数在离散傅里叶变换后的N个点值,由点值乘出新多项式后,再将其逆变换回系数表示法。
我们先来观察式子
D
F
T
DFT
DFT的式子。 我们为了简化式子,我们把
e
2
π
i
k
N
e^{\frac{2\pi ik}{N}}
eN2πik?用
W
N
W_{N}
WN?代替。(
W
N
W_{N}
WN?是一个关于
k
k
k的函数) 得到式子如下。
X
(
k
)
=
∑
n
=
0
N
?
1
x
(
n
)
W
N
n
(
k
)
X(k)=\sum_{n=0}^{N-1}x(n)W_{N}^{n}(k)
X(k)=n=0∑N?1?x(n)WNn?(k) 然后观察
W
N
W_{N}
WN?如下。
W
N
(
k
)
=
e
2
π
i
N
=
c
o
s
(
2
π
k
N
)
+
i
s
i
n
(
2
π
k
N
)
W_{N}(k)=e^{\frac{2\pi i}{N}}=cos(\frac{2\pi k}{N})+isin(\frac{2\pi k}{N})
WN?(k)=eN2πi?=cos(N2πk?)+isin(N2πk?) 发现
W
N
W_{N}
WN?是一个周期为
N
N
N的函数。
由此,我们发现变换后的函数
X
(
k
)
X(k)
X(k)是由
N
N
N个周期为
N
N
N的函数的幂次,乘上一个系数,相加而成。且这
N
N
N个函数的实部和虚部都是三角函数(
c
o
s
cos
cos和
s
i
n
sin
sin)。
我们很轻易地发现了
W
N
(
k
)
=
W
N
(
k
+
N
)
W_{N}(k)=W_{N}(k+N)
WN?(k)=WN?(k+N),但是这对我们来说不重要。更重要的性质源于三角函数的另一个性质——对称性。 我们把
[
0
,
2
π
]
[0,2\pi]
[0,2π]分成两部分
[
0
,
π
)
[0,\pi)
[0,π),和
(
π
,
2
π
]
(\pi,2\pi]
(π,2π],可以发现我们算出左边
t
t
t 处的值,就可以得到右边
π
+
t
\pi +t
π+t处的值。 说白了就是
s
i
n
(
t
)
=
?
s
i
n
(
π
+
t
)
sin(t)=-sin(\pi +t)
sin(t)=?sin(π+t)和
c
o
s
(
t
)
=
?
c
o
s
(
π
+
t
)
cos(t)=-cos(\pi +t)
cos(t)=?cos(π+t)。
对应到上面就得到了一个结论,我们只要算出了
W
N
(
t
)
W_{N}(t)
WN?(t),就可以得到
W
N
(
N
2
+
t
)
W_{N}(\frac{N}{2}+t)
WN?(2N?+t)。即
W
N
(
t
)
=
?
W
N
(
N
2
+
t
)
W_{N}(t)=-W_{N}(\frac{N}{2}+t)
WN?(t)=?WN?(2N?+t)。
上面只是简单的性质推导,正式的傅里叶变换在下面。
总而言之就是要分治,这个式子可以和分治有机结合。(我也不知道他怎么想出来的,公式推导在《数字信号处理》第四版的P122) 我们把枚举的奇数项和偶数项分离开。
X
(
k
)
=
∑
n
为
偶
数
x
(
n
)
W
N
n
(
k
)
+
∑
n
为
奇
数
x
(
n
)
W
N
n
(
k
)
=
∑
r
=
0
N
2
?
1
x
(
2
r
)
W
N
2
r
(
k
)
+
∑
r
=
0
N
2
?
1
x
(
2
r
+
1
)
W
N
2
r
+
1
(
k
)
=
∑
r
=
0
N
2
?
1
x
1
(
r
)
W
N
2
r
(
k
)
+
W
N
(
k
)
∑
r
=
0
N
2
?
1
x
2
(
r
)
W
N
2
r
(
k
)
=
∑
r
=
0
N
2
?
1
x
1
(
r
)
W
N
2
r
(
k
)
+
W
N
(
k
)
∑
r
=
0
N
2
?
1
x
2
(
r
)
W
N
2
r
(
k
)
=
X
1
(
k
)
+
W
N
(
k
)
X
2
(
k
)
X(k)=\sum_{n为偶数}x(n)W_{N}^{n}(k)+\sum_{n为奇数}x(n)W_{N}^{n}(k) \\=\sum_{r=0}^{\frac{N}{2}-1}x(2r)W_{N}^{2r}(k)+\sum_{r=0}^{\frac{N}{2}-1}x(2r+1)W_{N}^{2r+1}(k) \\=\sum_{r=0}^{\frac{N}{2}-1}x_{1}(r)W_{N}^{2r}(k)+W_{N}(k)\sum_{r=0}^{\frac{N}{2}-1}x_{2}(r)W_{N}^{2r}(k) \\=\sum_{r=0}^{\frac{N}{2}-1}x_{1}(r)W_{\frac{N}{2}}^{r}(k)+W_{N}(k)\sum_{r=0}^{\frac{N}{2}-1}x_{2}(r)W_{\frac{N}{2}}^{r}(k) \\=X_{1}(k)+W_{N}(k)X_{2}(k)
X(k)=n为偶数∑?x(n)WNn?(k)+n为奇数∑?x(n)WNn?(k)=r=0∑2N??1?x(2r)WN2r?(k)+r=0∑2N??1?x(2r+1)WN2r+1?(k)=r=0∑2N??1?x1?(r)WN2r?(k)+WN?(k)r=0∑2N??1?x2?(r)WN2r?(k)=r=0∑2N??1?x1?(r)W2N?r?(k)+WN?(k)r=0∑2N??1?x2?(r)W2N?r?(k)=X1?(k)+WN?(k)X2?(k) 其中
X
1
X_{1}
X1?是
x
1
x_{1}
x1?的傅里叶变换,
X
2
X_{2}
X2?是
x
2
x_{2}
x2?的傅里叶变换。 其中有一步
W
N
2
r
(
k
)
=
W
N
2
r
W_{N}^{2r}(k)=W_{\frac{N}{2}}^{r}
WN2r?(k)=W2N?r?前面忘记证了,想必读者应该一下子就能证出来了。
到此位置,所有准备工作都做完了,容易发现只要分治做好两个小多项式的点值,大的自然就出来了。所以可以分治做。
最后式子如下。
X
(
k
)
=
X
1
(
k
)
+
W
N
k
X
2
(
k
)
,
k
=
0
,
1
,
…
,
N
2
?
1
X
(
k
+
N
2
)
=
X
1
(
k
)
?
W
N
k
X
2
(
k
)
,
k
=
0
,
1
,
…
,
N
2
?
1
X(k)=X_{1}(k)+W^{k}_{N}X_{2}(k),k=0,1,…,\frac{N}{2}-1 \\X(k+\frac{N}{2})=X_{1}(k)-W^{k}_{N}X_{2}(k),k=0,1,…,\frac{N}{2}-1
X(k)=X1?(k)+WNk?X2?(k),k=0,1,…,2N??1X(k+2N?)=X1?(k)?WNk?X2?(k),k=0,1,…,2N??1
但是有一个细节问题,计算
W
N
(
k
)
W_{N}(k)
WN?(k)的复杂度是
o
(
l
o
g
n
)
o(logn)
o(logn)。而在分治的每一层我们需要求
N
N
N次
W
W
W函数,总共分治
l
o
g
n
logn
logn层,最终导致我们的复杂度变成了
o
(
n
l
o
g
n
l
o
g
n
)
o(nlognlogn)
o(nlognlogn)。 那么解决办法是前面提的
W
N
(
t
)
=
?
W
N
(
N
2
+
t
)
W_{N}(t)=-W_{N}(\frac{N}{2}+t)
WN?(t)=?WN?(2N?+t),我们每层只需要计算一半的
W
W
W,也就是
N
2
\frac{N}{2}
2N?个
W
W
W,于是在计算
W
W
W上的总开销就变成了
o
(
n
2
l
o
g
n
l
o
g
n
)
o(\frac{n}{2}lognlogn)
o(2n?lognlogn)。有锅巴用。
然后又发现一个可以优化的地方。既然
W
N
(
k
)
=
?
W
N
(
N
2
+
k
)
W_{N}(k)=-W_{N}(\frac{N}{2}+k)
WN?(k)=?WN?(2N?+k)来减少
W
W
W的计算次数(主要是减少
s
i
n
sin
sin和
c
o
s
cos
cos的计算次数),主要是来源于三角函数知道
[
0
,
π
]
[0,\pi]
[0,π]可以推演出
[
π
,
2
π
]
[\pi,2\pi]
[π,2π]。这样只需要在每层算
N
2
\frac{N}{2}
2N?次
c
o
s
cos
cos和
s
i
n
sin
sin,所以我们再往下细化一层,使得每层只需要计算
N
4
\frac{N}{4}
4N?次
c
o
s
cos
cos和
s
i
n
sin
sin。 那么同理,三角函数知道
[
0
,
π
2
]
[0,\frac{\pi}{2}]
[0,2π?]就可以推算出
[
π
2
,
2
π
]
[\frac{\pi}{2},2\pi]
[2π?,2π]。 所以,我们开始推
W
N
(
k
+
N
4
)
W_{N}(k+\frac{N}{4})
WN?(k+4N?)。
W
N
(
k
+
N
4
)
=
c
o
s
(
2
π
k
N
+
π
2
)
+
i
s
i
n
(
2
π
k
N
+
π
2
)
=
?
s
i
n
(
2
π
k
N
)
+
i
c
o
s
(
2
π
k
N
)
W_{N}(k+\frac{N}{4})=cos(\frac{2\pi k}{N}+\frac{\pi}{2})+isin(\frac{2\pi k}{N}+\frac{\pi}{2})=-sin(\frac{2\pi k}{N})+icos(\frac{2\pi k}{N})
WN?(k+4N?)=cos(N2πk?+2π?)+isin(N2πk?+2π?)=?sin(N2πk?)+icos(N2πk?) 然后,我们发现,我们对于某个
k
k
k计算出的
s
i
n
sin
sin和
c
o
s
cos
cos函数,可以沿用至
k
+
N
4
k+\frac{N}{4}
k+4N?,
k
+
N
2
k+\frac{N}{2}
k+2N?,
k
+
3
N
4
k+\frac{3N}{4}
k+43N?。 所以,在每一层,我们只需要计算
N
4
\frac{N}{4}
4N?次
W
W
W。 然后复杂度被优化到了
o
(
n
4
l
o
g
n
l
o
g
n
)
o(\frac{n}{4}lognlogn)
o(4n?lognlogn)。仍然没有任何卵用。
其实我们只需要算
W
N
(
1
)
W_{N}(1)
WN?(1),有
W
N
(
k
+
1
)
=
W
N
(
k
)
×
W
N
(
1
)
W_{N}(k+1)=W_{N}(k)\times W_{N}(1)
WN?(k+1)=WN?(k)×WN?(1),这才是真的有用。证明可以考虑这个复数的几何意义,就是把位于圆心的单位圆划等分成了
N
N
N个部分,取出第一个部分就相当于是一个旋转矩阵,幂次就是旋转的次数。 于是,我们只要在开始用三角函数算出
W
N
(
1
)
W_{N}(1)
WN?(1),后面一直乘上它就行了。
然后就做完了。
关于转换回系数,也就是逆变换,发现式子和这个差不多,所以做法也基本一样。记得最后要除以
N
N
N。这里就不重复介绍了。
为了方便起见,一般先把多项式长度拉到2的幂次,然后再进行变换,方便递归到底层为1。反过来说,要使用FFT板子时,注意长度应为2的幂次。 虽然是分治,但为了常数考虑,一般都用非递归版的写法。
递归版
递归版常数很大,强烈建议找个常数小的非递归版代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const double pi = acos(-1);
const int MAXN = 3000005;
struct cp
{
double s,v;
cp(double ss=0,double vv=0){
s=ss;v=vv;
}
};
cp operator * (cp a,cp b)
{
return cp(a.s*b.s-a.v*b.v,a.s*b.v+a.v*b.s);
}
cp operator + (cp a,cp b)
{
return cp(a.s+b.s,a.v+b.v);
}
cp operator - (cp a,cp b)
{
return cp(a.s-b.s,a.v-b.v);
}
cp operator / (cp a,int b)
{
return cp(a.s/b,a.v/b);
}
double coss[MAXN],sinn[MAXN];
void fft(cp *a,int len,int opt)
{
if(len==1)return ;
static cp b[MAXN];
int mid=len>>1,mid_2=mid>>1;
for(int i=0;i<len;i++)b[(i>>1)|(i&1?mid:0)]=a[i];
for(int i=0;i<len;i++)a[i]=b[i];
fft(a,mid,opt);fft(a+mid,mid,opt);
cp uni(coss[mid],opt*sinn[mid]),w(1,0);
for(int k=0;k<len;k++)
{
if(k<mid)b[k]=a[k]+w*a[k+mid];
else b[k]=a[k-mid]+w*a[k];
w=w*uni;
}
for(int i=0;i<len;i++)a[i]=b[i];
}
void mul(cp *a,cp *b,int len)
{
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++)a[i]=a[i]/len;
}
string sa,sb,sans;
cp a[MAXN],b[MAXN];
int ans[MAXN];
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>sa>>sb;
int n=sa.size(),m=sb.size();
int len=1;
while(len<n+m-1)coss[len]=cos(pi/len),sinn[len]=sin(pi/len),len<<=1;
for(int i=0;i<len;i++)
{
a[i]=i<n?cp(sa[n-i-1]-'0',0):cp(0,0);
b[i]=i<m?cp(sb[m-i-1]-'0',0):cp(0,0);
}
mul(a,b,len);
for(int i=0;i<len;i++)
{
ans[i]+=round(a[i].s);
ans[i+1]+=ans[i]/10;
ans[i]%=10;
}
int flag=0;
for(int i=len-1;i>=0;i--)
{
if(ans[i]==0&&flag==0)continue;
else
{
flag=1;
sans+=(char)(ans[i]+'0');
}
}
cout<<sans<<endl;
return 0;
}
非递归版
|