用途: 计算多项式卷积。
A(x)=a0+a1x+a2x2+⋅⋅⋅+an−1xn−1
B(x)=b0+b1x+b2x2+⋅⋅⋅+bn−1xn−1
求 : A(x)B(x)
前置芝士
多项式性质: 用任意 n 个多项式函数图像上的点,可唯一确定一个 n−1 次多项式。
复数,不会的话右转数学选修2-2.
做法
将 A(x)B(x) 的系数表示法转化为点表示法,
即 (x1,A(x1)) (x2,A(x2)) ⋅⋅⋅ (xn,A(xn))
和 (x1,B(x1)) (x2,B(x2)) ⋅⋅⋅ (xn,B(xn))
那么 A(x)B(x) 就可以表示为
(x1,A(x1)B(x1)) (x2,A(x2)B(x2)) ⋅⋅⋅ (xn,A(xn)B(xn))
再将其转化为系数表示法。
下面我们考虑如何转化为系数表示法。
点的横坐标可以选择复数域上的单位根。
将复平面中以原点为圆心的单位圆等分为 n 份,以 ωnk 表示从 x 正半轴一次逆时针取 n 份中的 k 份, ωnk 就被称为 n 次单位根。
n 次单位根的性质:
- ωni=ωnj ∀i=j
- ωnk=cosn2kπ+sinn2πi
- ωn0=ωnn=1
- ω2n2k=ωnk
- ωnk+2n=−ωnk
那么点表示法选取的横坐标为: ωn0 ωn1 ωn2⋅⋅⋅ωnn−1
现在,需要快速计算下面这个式子:
(ωn0,A(ωn0) (ωn1,A(ωn1)) ⋅⋅⋅ (ωnn−1,A(ωnn−1))
快速傅里叶正变换(系数表示法转化为点表示法)
A(x)=a0+a1x+a2x2+⋅⋅⋅+an−1xn−1 n为2的整次幂=(a0+a2x2+a4x4+⋅⋅⋅+an−2xn−2)+(a1x+a3x3+a5x5+⋅⋅⋅an−1xn−1) 奇偶次项分开
设:
A1(x)=a0+a2x+a4x2+⋅⋅⋅+an−2x2n−1
A2(x)=a1+a3x+a5x2+⋅⋅⋅+an−1x2n−1
那么可以得到:
A(x)=A1(x2)+xA2(x2)
k∈[0,n−1] , 将值域分为两段 [0,2n−1] , [2n,n−1]
如果 k∈[0,2n−1] , 那么有 (ωnk)2=ωn2k
A(ωnk)=A1(ωn2k)+ωnkA2(ωn2k)=A1(ω2nk)+ωnkA2(ω2nk)
如果 [2n,n−1] , 将这一段全部减去 2n , k 还是遍历 [0,2n−1] 将 k 加上 2n 就行了。
A(ωnk+2n)=A1(ωn2k+n)+ωnk+2nA2(ωn2k+n)=A1(ωn2k)−ωnkA2(ωn2k)=A1(ω2nk)−ωnkA2(ω2nk)
发现 A(ωnk) , A(ωnk+2n) 计算非常相似,只要求出 A1(ω2nk) , A2(ω2nk) 即可。
这个过程可以迭代 ,每次将区间分为两段 ,最多会有 log n 层 ,总复杂度 O(nlog n) .
迭代划分(假设有 23 项)
初始序列 [x0,x1,x2,x3,x4,x5,x6,x7]
奇偶划分一次 [x0,x2,x4,x6],[x1,x3,x5,x7]
将这两段继续奇偶划分 [x0,x4],[x2,x6],[x1,x5],[x3,x7]
接着分 [x0],[x4],[x2],[x6],[x1],[x5],[x3],[x7]
发现最后一层每一位下标是第一层下标二进制反过来(比如第二项 (1)10=(001)2 , (4)10=(100)2)
我们可以递推计算有 2bit 项的多项式最后一层的第 i 位下标 chgi。
chgi=(2chg2i)∣((i mod 2)∗2bit−1)
也就是
chg[i]=(chg[i>>1]>>1)|((i&1)<<(bit-1));
理解一下就是: 对于一个数i,找到没有 i 的第 0 位的数的反转之后的结果,将这个结果右移一位去掉最高位,再把 i 的第 0 位放到最高位,就实现了二进制反转.
for example : 计算到 6 时,将 (6)10=(110)2 的二进制表示去掉第 0 位,也就是 (3)10=(011)2 ,3 的结果之前处理过了,是 (110)2=(6)10,(这个例子好失败啊),将反转后的结果右移 1 位 变成了 (011)2 ,然后把 6 的第 0 位(还是 0) ,放到这个数的最高位,就变成了 (011)2=(3)10。
快速傅里叶逆变换(点表示法转化为系数表示法)
有点 (ωn0,A(ωn0)) (ωn1,A(ωn1))⋅⋅⋅(ωnn−1,A(ωnn−1)).
求 A(x)=a0+a1x+a2x2+⋅⋅⋅+an−1xn−1
设 yk=ωnk 则有
ck=∑i=0n−1yi(ωn−k)i=nak
证明:
ck=i=0∑n−1yi(ωn−k)i=i=0∑n−1(j=0∑n−1aj(ωni)j))(ωn−k)i=i=0∑n−1(j=0∑n−1aj(ωnj)i(ωn−k)i=i=0∑n−1(j=0∑n−1aj(ωnj−k)i=j=0∑n−1aj(i=0∑n−1(ωnj−k)i)
设 S(x)=1+x+x2+⋅⋅⋅+xn−1 .
k=0 时 , 有:
S(ωnk)=ωn0+ωnk+ωn2k+⋅⋅⋅+ωn(n−1)k
ωnkS(ωnk)=ωnk+ωn2k+ωn3k+⋅⋅⋅+ωn0
两柿相减,有:
(1−ωnk)S(ωnk)=0
故 S(ωnk)=0
k=0 时 , S(ωnk)=n ,代入显然
综上 : 把 S 代入柿子可知:
ci=nai
证毕.
设 S(x)=1+x+x2+⋅⋅⋅+xn−1 .
设 B(x)=y0+y1x1+y2x2+⋅⋅⋅+yn−1xn−1.
则有 ci=B(ωn−i) ,然后用上面说的正变换求一下就行。
FFT板子
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 3000005
#define LL long long
#define LB long double
using namespace std;
int n,m,bit,tot,chg[N];
const LB pi=3.1415926535897932384626433832795028841;
struct complex
{
LB x,y;
complex operator+ (const complex &tmp) const
{
return (complex){x+tmp.x,y+tmp.y};
}
complex operator- (const complex &tmp) const
{
return (complex){x-tmp.x,y-tmp.y};
}
complex operator* (const complex &tmp) const
{
return (complex){x*tmp.x-y*tmp.y,x*tmp.y+y*tmp.x};
}
}a[N],b[N];
inline int qr()
{
char ch=0;int w=1,x=0;
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch<='9'&&ch>='0'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
return x*w;
}
inline void FFT(complex a[],int opt)
{
for(register int i=0;i<tot;i++)
if(i<chg[i])
swap(a[i],a[chg[i]]);
for(register int mid=1;mid<tot;mid<<=1)
{
complex w1=(complex){cos((LB)pi/mid),(LB)opt*sin((LB)pi/mid)};
for(register int i=0;i<tot;i+=(mid<<1))
{
complex wk=(complex){1,0};
for(register int j=0;j<mid;j++,wk=wk*w1)
{
complex x=a[i+j];
complex y=wk*a[i+mid+j];
a[i+j]=x+y;
a[i+j+mid]=x-y;
}
}
}
}
int main()
{
n=qr();
m=qr();
for(register int i=0;i<=n;i++)
a[i].x=qr();
for(register int i=0;i<=m;i++)
b[i].x=qr();
while((1<<bit)<m+n+1)
bit++;
tot=(1<<bit);
for(register int i=0;i<tot;i++)
chg[i]=(chg[i>>1]>>1)|((i&1)<<(bit-1));
FFT(a,1);
FFT(b,1);
for(register int i=0;i<tot;i++)
a[i]=a[i]*b[i];
FFT(a,-1);
for(register int i=0;i<m+n+1;i++)
printf("%d ",(int)(a[i].x/tot+0.5));
printf("\n");
return 0;
}