[拉格朗日反演][FFT][NTT][多项式大全]详解
1、目录
2、多项式的两种表示法
1.系数表示法
我们最常用的多项式表示法就是系数表示法,一个次数界为的多项式可以用一个向量系数表示如下:
系数表示法很适合做加法,可以在的时间复杂度内完成,表达式为:
当中
但是,系数表示法不适合做乘法,时间复杂度为,表达式为:
当中
这就是卷积的一般形式,记,我们要想办法加速这个过程。
2.点值表示法
顾名思义,点值就是多项式在一个点处的值。多项式的点值表达是一个集合:
使得对于有两两不相同且。
个点可以确定唯一一个次多项式。
点值表达有很多优良的性质,加法和乘法都可以在的时间复杂度内完成。
现有的点值表达
则的点值表达为:
的点值表达为:
可见,点值表示可以帮助我们更快地进行卷积,可是如何在系数表示法和点值表示法之间相互转化呢?
3、复数
当为实数时,无法很好地对转换方法进行优化。为了优化计算所浪费的时间,我们需要有循环的性质。但点值表示法需要个两两不同的值,而在实数域中只有和,因此,我们需要复数的帮助。
1.复数、复平面的定义
我们把形如的数称为复数,其中为实部(Real),记为;为虚部(Imaginary),记为。
每一点都对应唯一复数的平面叫复平面,相当于一个把作为横坐标,把作为纵坐标的笛卡尔坐标系。如图:
模长:复平面上原点到复数的距离,记为。根据勾股定理,
辐角:复平面上轴与复数所对应向量之间的夹角,在之间的记为辐角主值。
2.欧拉公式
大名鼎鼎的欧拉公式:
根据三角函数在单位圆上的几何意义,公式是容易理解的。
几何意义:
当中为角度,为弧长。
则根据欧拉公式,可将一个复数表示为一个二元组,即模长和辐角(相当于复平面上极坐标系的表示方法)。值为:
特殊情况:欧拉恒等式
3.复数的运算
(1)复数加法
运算规则:实部、虚部分别相加
几何意义:如图
结果相当于两个向量所构成的平行四边形的对角线。如果把一个复数所对应的向量视为一个移动的变换,那么向量加法就是连续运用这两个变换相当于的新变换。
(2)复数乘法
运算规则:展开
几何意义:如图
如图,,
总结就是:模长相乘,辐角相加。
因此,如果模长为,那么它的次方一定还在单位圆上。
证明:
根据欧拉公式,已知
则
证毕。
4.单位复数根
(1)基本性质
单位复数根是方程的解,第个解记为(这里的事实上是乘方的含义)
的解在复平面上的位置如下:
可以看到,个解把单位圆分成了等弧,交点即为根。而且,实际上是的次方,模长仍为,辐角翻倍!
为什么呢?
这就很明显了。
所以,事实上表示的是的次幂。为什么选择单位复数根呢?因为它有循环的优良性质,即。由于其他的都可以由得到,因此称为主次单位根,又记为。
根据单位复数根的平分圆的意义和欧拉公式,。
(2)计算引理
显然,由于单位复数根循环(),有变换恒等式:
每一份再分成份,编号也变成倍,位置自然不变(),所以有消去引理:
由于过了就会绕过半圈(),所以有折半引理:
对单位复数根求和,根据几何级数(等差数列求和公式),可得(),即有求和引理(要注意公式的使用条件):
4、DFT&FFT
1.DFT
DFT就是求多项式在点处取值的过程。即:
结果就是的离散傅里叶变换(DFT),记为
2.FFT
(1)递归
DFT的算法太慢了,FFT使用分治策略优化速度到。
考虑奇偶分治。
现在,我们假设,设原系数,分治为偶数部分,奇数部分,已经递归求出,,现在我们要合并,得到(蝴蝶操作)。
对于的边界情况,结果是显然的:因为,故,即结果等于原系数。
对于,现在我们枚举要合并出:
-
对于:
-
对于:
我们用替代,就得到
结合在一起就得到
这样我们就可以把两个长的序列合并为一个长的序列了。
以下图的递归序,就可以在的时间复杂度内完成求解了。
(2)迭代
递归方法消耗内存、时间过大,无法承受。我们每次把下标分为奇数部分和偶数部分,是否有办法直接求出最后的递归运算顺序,以避免递归呢?
这样想:
第一次奇偶划分,我们按照二进制的倒数第一位排序;
第二次奇偶划分,我们按照二进制的倒数第二位排序;
第次奇偶划分,我们按照二进制的倒数第位排序;
依此类推。
因此,结果顺序就是原序列按照二进制位翻转的大小排序的结果。只要依次交换,求出序列,就可以用迭代方法相邻归并实现快速傅里叶变换。
或者,我们也可以用更加代数的方法来发现这个结论。
已知现在位置为,按照奇偶重排:
- 若,则位置变为,即为把最后一位提到第一位。
- 若,则位置变为,同样是把最后一位提到第一位。
则反复次之后,就相当于二进制反转了。
如时,求出二进制:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|
翻转:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|
按翻转后的值排序:
0 | 4 | 2 | 6 | 1 | 5 | 3 | 7 |
---|---|---|---|---|---|---|---|
这样就可以把奇偶合并转化为左右归并,迭代实现了。
5、IDFT&IFFT
何把点值表达变回系数表达呢?如果把求值写成矩阵形式,就是:
如果我们要把变成,就需要求出第一个矩阵的逆,即:
这个范德蒙德矩阵极为特殊,它的逆矩阵是:
只是把每项取倒数,并将结果除以即可。
证明:
记原矩阵为,我们给出的矩阵为。
- 当时:
- 当时:
综上所述,,即
以上,离散傅里叶逆变换(IDFT)的表达式为:
记。
同理,可以用相同的方法把IDFT加速到,称为IFFT。
6、NTT
1.定义
有时候我们会想要模素数意义下的多项式乘法。此时,由次数界为的多项式的系数表达求的系数表达的公式为:
FFT无能为力,我们需要一种新的DFT,以数论的办法进行,这就是快速数论变换(NTT)。
2.原根
(1)定义
我们需要一种有数论循环性质的新数,原根恰好满足我们的要求。
设为正整数,为整数,若模的阶等于,则称为模的一个原根。
假设是素数的原根,有,且对于,有的结果两两不同,且。
可以发现,原根同样有循环的性质。因此,我们类比的定义,把原来的替换为。
(2)性质
我们来证明一些类似单位复数根的性质。
变换恒等式:
因为:
所以:
消去引理:
显然有:
折半引理:
因为:
所以若要使:
成立,即:
只需证:
由于:
那么我们可以设:
因为:
所以:
即:
又因为为素数,所以有:
所以:
又因为:
所以:
即:
得证:
求和引理:
这样,我们就证明了原根由于复数单位根相同的性质。
3.公式
我们用原根替换复数单位根,得到:
即。逆变换:
即。
7、其他扩展
1.任意模数FFT
有的时候我们需要卷积后模上一个数,这个数不是NTT模数,甚至可能不是个质数。那我们该怎么做呢?
这里使用拆系数FFT,本质是以时间换精度。
现在给定次数界为的两个多项式,要求。
首先,令,再对于每个或,把其变为的形式。这样,和就都小于等于了。
那么卷积就可以写成:
那么我们看到,可以由和合并得到。那么我们对分别做FFT,再对应算出,对它们再分别做IFFT,就可以由得到答案了。
这么做的好处究竟在哪里呢?事实上,计算后的最大值由变为了,避免了溢出。
时间复杂度:
2.多项式求逆
现在我们有一个次数界为的多项式,要求满足。
我们考虑倍增实现。
- 当时,直接求逆元求得。
- 当时,已有:
因为:
又因为除了次项之外到次都为,因此到次项也为零:
又
两式相减:
因为:
所以:
既然至次项都为零,那么平方之后至次项也为零:
又
两边同时乘上,得:
移项,得:
这样就可以了。
时间复杂度:
3.多项式开根
前置:多项式求逆。
现在我们有一个次数界为的多项式,要求满足。
还是倍增。
- 当时,等于在模意义下的开根。
- 当时,已有:
因为:
移项,得:
两边平方,同理可得:
所以:
即:
除过去:
得到:
即:
这就可以了。
时间复杂度:
4.多项式求导
根据导数的可加性和幂函数求导法则,有多项式的导数为:
时间复杂度:
5.多项式积分
根据积分的可加性和幂函数的不定积分,有多项式的不定积分为:
时间复杂度:
6.多项式求ln
前置:多项式求导&积分&求逆
现在已知多项式,要求。我们两边微分,得到:
再两边积分,就得到:
因此,我们直接多项式求导+求逆+积分解决。
时间复杂度:
7.多项式求exp
前置:多项式求ln
现在,我们已知多项式(这样写是因为在这里把视为是与无关的),求。只要我们设,就得到:
我们考虑用牛顿迭代法来倍增解这个方程。
对于牛顿迭代法的初始解,即结果的常数项,我们并不知道具体值。但是如果不对的话,也只是缺少了一个常数罢了,那我们不妨设。
倍增:现在设我们已经求出了的前项,即:
根据牛顿迭代法,我们求出下一个近似解为:
如此,就可以倍增实现了。
时间复杂度:
8.多项式求幂
已知多项式和指数,求。
在幂数很大的时候,为了确定出系数,时间复杂度为,我们必须进行优化。
我们发现:
于是我们可以用多项式求ln+多项式求exp在的时间内求出。
8、代码实现
1.二进制反转
可以用一种类似dp的思想计算。
边界:的二进制翻转为
递归式:对于,已经算出了,就是除去最后一位的二进制翻转()向后移动一位再补上第一位。即:
rev[a]=(rev[a>>1]>>1)|((a&1)<<(l-1))
为要翻转的位数。
2.复数类
套公式即可。
struct complex{
double re,im;
complex(double re=0,double im=0):re(re),im(im){}
complex operator+(complex &b)const{return complex(re+b.re,im+b.im);}
complex operator-(complex &b)const{return complex(re-b.re,im-b.im);}
complex operator*(complex &b)const{return complex(re*b.re-im*b.im,re*b.im+im*b.re);}
};
3.FFT
- 1:根据二进制翻转交换和
- 2:枚举归并步长,为二的幂;
- 2.1: 根据欧拉公式求出
- 2.2:枚举归并位置,归并和,步长为
- 2.2.1:枚举的幂数进行蝴蝶操作计算,根据单位根计算
- 2.2.1.1:
- 2.2.1.2:
- 2.2.1:枚举的幂数进行蝴蝶操作计算,根据单位根计算
注意:由于在C++中值会被更改,因此需要引入临时变量。
void FFT(complex c[]){
for(int i=0;i<n;i++)if(i<r[i])swap(c[i],c[r[i]]);
for(int i=1;i<n;i<<=1){
complex omn(cos(pi/i),sin(pi/i));
for(int j=0;j<n;j+=(i<<1)){
complex om(1,0);
for(int k=0;k<i;k++,om=om*omn){
complex x=c[j+k],y=om*c[j+k+i];
c[j+k]=x+y;
c[j+k+i]=x-y;
}
}
}
}
4.IFFT
由于公式中只差一个负号而已,因此引入一个参数,在欧拉公式的地方乘上去,再除以就可以了。
void FFT(complex c[],int tp=1){
for(int i=0;i<n;i++)if(i<r[i])swap(c[i],c[r[i]]);
for(int i=1;i<n;i<<=1){
complex omn(cos(pi/i),tp*sin(pi/i));
for(int j=0;j<n;j+=(i<<1)){
complex om(1,0);
for(int k=0;k<i;k++,om=om*omn){
complex x=c[j+k],y=om*c[j+k+i];
c[j+k]=x+y;
c[j+k+i]=x-y;
}
}
}
}
void IFFT(complex c[]){
FFT(c,-1);
for(int i=0;i<=m;i++)a[i].re/=n;
}
5.多项式乘法
模板:洛谷3083
注意:
1、由于FFT要求为的幂且结果的次数界较大,所以要把两个因式的系数补到位,满足且大于等于因式的次数界。
2、虽然在数学上精准,但在C++中误差巨大,因此虚部不会为,忽略即可。实部也不为正数,可以加上再向下取整。
代码:
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
const double pi=acos(-1);
struct complex{
double re,im;
complex(double re=0,double im=0):re(re),im(im){}
complex operator+(complex &b)const{return complex(re+b.re,im+b.im);}
complex operator-(complex &b)const{return complex(re-b.re,im-b.im);}
complex operator*(complex &b)const{return complex(re*b.re-im*b.im,re*b.im+im*b.re);}
}a[2097153],b[2097153];
int n,m,l,r[2097153];
void FFT(complex c[],int tp=1){
for(int i=0;i<n;i++)if(i<r[i])swap(c[i],c[r[i]]);
for(int i=1;i<n;i<<=1){
complex omn(cos(pi/i),tp*sin(pi/i));
for(int j=0;j<n;j+=(i<<1)){
complex om(1,0);
for(int k=0;k<i;k++,om=om*omn){
complex x=c[j+k],y=om*c[j+k+i];
c[j+k]=x+y;
c[j+k+i]=x-y;
}
}
}
}
void IFFT(complex c[]){
FFT(c,-1);
for(int i=0;i<=m;i++)a[i].re/=n;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=0;i<=n;i++)scanf("%lf",&a[i].re);
for(int i=0;i<=m;i++)scanf("%lf",&b[i].re);
m+=n;
for(n=1;n<=m;n<<=1)l++;
for(int i=0;i<n;i++)r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
FFT(a);
FFT(b);
for(int i=0;i<=n;i++)a[i]=a[i]*b[i];
IFFT(a);
for(int i=0;i<=m;i++)printf("%d ",int(a[i].re+0.5));
}
6.(I)FFT+(I)NTT
给出一个次多项式和一个次多项式的系数表达(),求乘积。时,直接计算;时,结果取模(原根为)。
注:为了方便阅读,代码效率不高。若要提速,可以把单位根打表。而且,由于中必须为整数,故仅在第一个比大的的整数次幂是的约数时才可行,此处,。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
const double pi=acos(-1);
const int p=998244353;
typedef long long ll;
struct complex{
double re,im;
complex(double re=0,double im=0):re(re),im(im){}
complex operator+(complex b)const{return complex(re+b.re,im+b.im);}
complex operator-(complex b)const{return complex(re-b.re,im-b.im);}
complex operator*(complex b)const{return complex(re*b.re-im*b.im,re*b.im+im*b.re);}
}af[131073],bf[131073];
struct modp{
int a;
modp(int a=0):a(a){}
modp operator+(modp b)const{return (a+b.a)%p;}
modp operator-(modp b)const{return (a-b.a+p)%p;}
modp operator*(modp b)const{return ll(a)*b.a%p;}
modp operator/(modp b)const{return (b^(p-2))*a;}
modp operator^(int b)const{
modp ans=1,bs=a;
while(b){
if(b&1)ans=ans*bs;
bs=bs*bs;
b>>=1;
}
return ans;
}
}an[131073],bn[131073];
const modp g=3;
int n,m,l,r[131073],type;
modp gn[18];
void init(){
m+=n;
for(n=1;n<=m;n<<=1)l++;
for(int i=0;i<n;i++)r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
for(int i=0;i<=17;i++)gn[i]=g^((p-1)/(1<<i));
}
void FFT(complex c[],int tp=1){
for(int i=0;i<n;i++)if(i<r[i])swap(c[i],c[r[i]]);
for(int i=1;i<n;i<<=1){
complex omn(cos(pi/i),tp*sin(pi/i));
for(int j=0;j<n;j+=(i<<1)){
complex om(1,0);
for(int k=0;k<i;k++,om=om*omn){
complex x=c[j+k],y=om*c[j+k+i];
c[j+k]=x+y;
c[j+k+i]=x-y;
}
}
}
}
void IFFT(complex c[]){
FFT(c,-1);
for(int i=0;i<=n;i++)c[i].re/=n;
}
void NTT(modp c[],int tp=1){
for(int i=0;i<n;i++)if(i<r[i])swap(c[i],c[r[i]]);
for(int i=1,id=1;i<n;i<<=1,id++){
modp ggn=gn[id]^(tp==1?1:p-2);
for(int j=0;j<n;j+=(i<<1)){
modp gg=1;
for(int k=0;k<i;k++,gg=gg*ggn){
modp x=c[j+k],y=gg*c[j+k+i];
c[j+k]=x+y;
c[j+k+i]=x-y;
}
}
}
}
void INTT(modp c[]){
NTT(c,-1);
for(int i=0;i<=n;i++)c[i]=c[i]/n;
}
int main(){
scanf("%d%d%d",&n,&m,&type);
if(type==0){
for(int i=0;i<=n;i++)scanf("%lf",&af[i].re);
for(int i=0;i<=m;i++)scanf("%lf",&bf[i].re);
}else{
for(int i=0;i<=n;i++)scanf("%d",&an[i].a);
for(int i=0;i<=m;i++)scanf("%d",&bn[i].a);
}
init();
if(type==0){
FFT(af);
FFT(bf);
for(int i=0;i<=n;i++)af[i]=af[i]*bf[i];
IFFT(af);
for(int i=0;i<=m;i++)printf("%d ",int(af[i].re+0.5));
}else{
NTT(an);
NTT(bn);
for(int i=0;i<=n;i++)an[i]=an[i]*bn[i];
INTT(an);
for(int i=0;i<=m;i++)printf("%d ",an[i].a);
}
printf("\n");
}
常数只有上面那个的三分之一的NTT(正式考试请务必采用这种写法):
PS:有一道题上面那个3700ms,这个1000ms
inline ll pow(ll a,int b){
ll ans=1;
while(b){
if(b&1)ans=ans*a%p;
a=a*a%p;
b>>=1;
}
return ans;
}
inline ll add(ll a,ll b){return a+b>p?a+b-p:a+b;}
inline ll cut(ll a,ll b){return a-b<0?a-b+p:a-b;}
void init(){
for(n=1;n<=s;n<<=1);
nn=n;
gn[0][0]=gn[1][0]=1;
gn[0][1]=pow(g,(p-1)/(n<<1));
gn[1][1]=pow(gn[0][1],p-2);
for(int i=2;i<(n<<1);i++){gn[0][i]=gn[0][i-1]*gn[0][1]%p;gn[1][i]=gn[1][i-1]*gn[1][1]%p;}
inv[1]=1;
for(int i=2;i<=(n<<1);i++)inv[i]=inv[p%i]*(p-p/i)%p;
}
void NTT(ll c[],int n,int tp=1){
for(int i=0;i<n;i++){
r[i]=(r[i>>1]>>1)|((i&1)*(n>>1));
if(i<r[i])swap(c[i],c[r[i]]);
}
for(int i=1;i<n;i<<=1){
for(int j=0;j<n;j+=(i<<1)){
for(int k=0;k<i;k++){
ll x=c[j+k],y=gn[tp!=1][nn/i*k]*c[j+k+i]%p;
c[j+k]=add(x,y);
c[j+k+i]=cut(x,y);
}
}
}
}
void INTT(ll c[],int n){
NTT(c,n,-1);
for(int i=0;i<n;i++)c[i]=c[i]*inv[n]%p;
}
7.任意模数FFT
给定,再给定次数界为的第一个多项式和次数界为的第二个多项式,求两个多项式的卷积模。
注意:拆系数FFT精度损失极大,需要使用long double保证正确性。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
typedef long long ll;
int x,n,m,l,r[262145],pm,p;
const long double pi=acos(-1);
struct complex{
long double re,im;
complex(long double re=0,long double im=0):re(re),im(im){}
complex operator+(const complex &b)const{return complex(re+b.re,im+b.im);}
complex operator-(const complex &b)const{return complex(re-b.re,im-b.im);}
complex operator*(const complex &b)const{return complex(re*b.re-im*b.im,re*b.im+im*b.re);}
}k1[262145],r1[262145],k2[262145],r2[262145],c1[262145],c2[262145],c3[262145];
void FFT(complex c[],int tp=1){
for(int i=0;i<n;i++)if(i<r[i])swap(c[i],c[r[i]]);
for(int i=1;i<n;i<<=1){
complex omn(cos(pi/i),tp*sin(pi/i));
for(int j=0;j<n;j+=(i<<1)){
complex om(1,0);
for(int k=0;k<i;k++,om=om*omn){
complex x=c[j+k],y=om*c[j+k+i];
c[j+k]=x+y;
c[j+k+i]=x-y;
}
}
}
}
void IFFT(complex c[]){
FFT(c,-1);
for(int i=0;i<=n;i++)c[i].re/=n;
}
void init(){
m+=n;
for(n=1;n<=m;n<<=1)l++;
for(int i=0;i<n;i++)r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
}
int main(){
scanf("%d%d%d",&n,&m,&p);
pm=sqrt(p);
for(int i=0;i<=n;i++){
scanf("%d",&x);
k1[i]=x/pm;
r1[i]=x%pm;
}
for(int i=0;i<=m;i++){
scanf("%d",&x);
k2[i]=x/pm;
r2[i]=x%pm;
}
init();
FFT(k1);
FFT(r1);
FFT(k2);
FFT(r2);
for(int i=0;i<=n;i++){
c1[i]=k1[i]*k2[i];
c2[i]=k1[i]*r2[i]+r1[i]*k2[i];
c3[i]=r1[i]*r2[i];
}
IFFT(c1);
IFFT(c2);
IFFT(c3);
for(int i=0;i<=m;i++){
ll s1=ll(c1[i].re+0.5)%p*pm%p*pm%p,s2=ll(c2[i].re+0.5)%p*pm%p,s3=ll(c3[i].re+0.5)%p;
printf("%lld ",((s1+s2)%p+s3)%p);
}
}
7.多项式的运算
依赖关系:
直接按照公式打就好了。
我们先修改NTT:
void NTT(modp c[],int n,int tp=1){
for(int i=0;i<n;i++){
r[i]=(r[i>>1]>>1)|((i&1)*(n>>1));
if(i<r[i])swap(c[i],c[r[i]]);
}
for(int i=1,id=1;i<n;i<<=1,id++){
modp ggn=gn[id]^(tp==1?1:p-2);
for(int j=0;j<n;j+=(i<<1)){
modp gg=1;
for(int k=0;k<i;k++,gg=gg*ggn){
modp x=c[j+k],y=gg*c[j+k+i];
c[j+k]=x+y;
c[j+k+i]=x-y;
}
}
}
}
void INTT(modp c[],int n){
NTT(c,n,-1);
for(int i=0;i<n;i++)c[i]=c[i]/n;
}
求逆:
void inverse(modp c[],int n=n){
static modp t[262145],tma[262145];
t[0]=c[0]^(p-2);
for(int k=2;k<=n;k<<=1){
for(int i=0;i<(k<<1);i++)tma[i]=(i<k?c[i]:0);
for(int i=(k>>1);i<(k<<1);i++)t[i]=0;
NTT(tma,k<<1);
NTT(t,k<<1);
for(int i=0;i<(k<<1);i++)t[i]=t[i]*2-t[i]*t[i]*tma[i];
INTT(t,k<<1);
}
memcpy(c,t,sizeof(int)*n);
}
开根():
void sqrt(modp c[],int n=n){
static modp t[262145],tma[262145],tmb[262145];
t[0]=1;
for(int k=2;k<=n;k<<=1){
for(int i=0;i<k;i++)tma[i]=t[i]*2;
inverse(tma,k);
for(int i=0;i<(k<<1);i++)tmb[i]=(i<k?c[i]:0);
NTT(tma,k<<1);
NTT(tmb,k<<1);
for(int i=0;i<(k<<1);i++){
modp tmp=tma[i];
tma[i]=t[i];
t[i]=tmp*tmb[i];
}
INTT(t,k<<1);
for(int i=0;i<(k<<1);i++)t[i]=(i<k?t[i]+tma[i]/2:0);
}
memcpy(c,t,sizeof(int)*n);
}
求导:
void derivative(modp c[],int n=n){for(int i=0;i<n;i++)c[i]=c[i+1]*(i+1);}
积分:
void integrate(modp c[],int n=n){for(int i=n-1;i>=1;i--)c[i]=c[i-1]*inv[i];c[0]=0;}
求ln:
void ln(modp c[],int n=n){
static modp t[262145];
for(int i=0;i<(n<<1);i++)t[i]=(i<n?c[i]:0);
derivative(t,n);
inverse(c,n);
NTT(t,n<<1);
NTT(c,n<<1);
for(int i=0;i<(n<<1);i++)c[i]=c[i]*t[i];
INTT(c,n<<1);
for(int i=n;i<(n<<1);i++)c[i]=0;
integrate(c,n);
}
求exp:
void exp(modp c[]){
static modp t[262145],ta[262145];
t[0]=1;
for(int k=2;k<=n;k<<=1){
for(int i=0;i<(k<<1);i++)ta[i]=t[i];
ln(ta,k);
for(int i=0;i<k;i++)ta[i]=c[i]-ta[i];
ta[0].a++;
NTT(t,k<<1);
NTT(ta,k<<1);
for(int i=0;i<(k<<1);i++)t[i]=t[i]*ta[i];
INTT(t,k<<1);
for(int i=k;i<(k<<1);i++)t[i]=0;
}
memcpy(c,t,sizeof(modp)*n);
}
求幂:
我们在多项式求exp中假定了,那么如果常数项不是的话我们就把常数项变为在运算后再用快速幂变回来即可。
void pow(modp c[],int k){
ln(c);
for(int i=0;i<n;i++)c[i]=c[i]*k;
exp(c);
}
9、拉格朗日反演
1.形式幂级数
对于任意一个域我们定义在其上的形式幂级数为:
记所有的形式幂级数为。
2.反演公式
拉格朗日反演是求关于函数方程的幂级数展开系数非常重要的工具,可以用于组合计数函数的系数提取。
公式内容:
这里指取中的系数。
若且,则称为的复合逆。满足:
特别的,如果,那么有:
公式的推导:
由式,得,代入式可得:
公式的推广: