形如 (f(x)=a_0x^0+a_1x^1+a_2x^2+ dots +a_{n-1}x^{n-1})。
点值表示法:通过代入 (n) 个不同的值 (x_0,x_1 dots x_{n-1}) 到 (f(x)) 中,得到 (y_0,y_1...y_{n-1}),用 ((x_0,y_0),(x_1,y_1) dots (x_{n-1},y_{n-1})) 即可表示这个多项式。
若用点值表示法,并且两个多项式的 (x) 对应相等,那么将 (y) 对应相乘,即可 (O(n)) 的得到它们乘积的点值表示法。
从多项式的系数表示向点值表示的转化,称为求值,从多项式的点值表示向系数表示的转化,称为插值。
FFT
快速傅里叶变换可以做到 (O(nlog n)) 进行求值和插值。
复数相乘,模长相乘,幅角相加。
在复平面上,以原点为圆心,(1) 为半径作圆,所得的圆叫单位圆。以圆点为起点,圆的 (n) 等分点为终点,做 (n) 个向量,设幅角为正且最小的向量对应的复数为 (ω_n),称为 (n) 次单位根。
进行奇偶分类得:
得 (f(x)=f1(x^2)+xf2(x^2))。
设 (k<frac{n}{2}),代入 (ω_n^k) 得:
再代入 (ω_n^{k+frac{n}{2}}) 得
通过这两个式子,我们就可以进行递归求解了,但因递归常数过大,我们通过迭代来实现。
我们将原多项式系数重新排序,将每个系数都在它递归的最后位置,就可以用迭代来代替递归实现。
发现原来在第 (a) 个位置的系数,在递归的最后位置为 (a) 的二进制反转以后的数。
这称为蝴蝶操作。
代入 (ω_n^{-k}),可再次求得一系列值,将其除以 (n) 即可求得多项式相乘后的系数表示。
struct Complex
{
double x,y;
Complex(double a=0,double b=0)
{
x=a,y=b;
}
}f[maxn],g[maxn];
Complex operator +(const Complex &a,const Complex &b)
{
return Complex(a.x+b.x,a.y+b.y);
}
Complex operator -(const Complex &a,const Complex &b)
{
return Complex(a.x-b.x,a.y-b.y);
}
Complex operator *(const Complex &a,const Complex &b)
{
return Complex(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);
}
int calc(int n)
{
int lim=1;
while(lim<=n) lim<<=1;
for(int i=0;i<lim;++i)
rev[i]=(rev[i>>1]>>1)|((i&1)?lim>>1:0);
return lim;
}
void FFT(Complex *a,int lim,int type)
{
for(int i=0;i<lim;++i)
if(i<rev[i])
swap(a[i],a[rev[i]]);
for(int len=1;len<lim;len<<=1)
{
Complex T(cos(Pi/len),type*sin(Pi/len));
for(int i=0;i<lim;i+=len<<1)
{
Complex t(1,0);
for(int j=i;j<i+len;++j,t=t*T)
{
Complex x=a[j],y=t*a[j+len];
a[j]=x+y,a[j+len]=x-y;
}
}
}
if(type==1) return;
for(int i=0;i<lim;++i) a[i].x=a[i].x/lim+0.5;
}
void mul(Complex *f,Complex *g)
{
int lim=calc(n+m);
FFT(f,lim,1),FFT(g,lim,1);
for(int i=0;i<lim;++i) f[i]=f[i]*g[i];
FFT(f,lim,-1);
}
NTT
若 (a,p) 互素,且 (p>1),对于 (a^n equiv 1 pmod{m}) 最小的 (n),称为 (a) 模 (p) 的阶,记作 (δ_p(a))。
设 (p) 是正整数,(a) 是整数,若 (δ_p(a)=varphi(p)),则称 (a) 为模 (p) 的一个原根。
原根个数不唯一。
若 (P) 为素数,设 (G) 为 (P) 的原根,那么 (G^i mod P,(i<G<P,0<i<P)) 的结果两两不同。
模数有 (998244353,1004535809,469762049),原根都为 (3)。
int calc(int n)
{
int lim=1;
while(lim<=n) lim<<=1;
for(int i=0;i<lim;++i)
rev[i]=(rev[i>>1]>>1)|((i&1)?lim>>1:0);
return lim;
}
void NTT(ll *a,int lim,int type)
{
for(int i=0;i<lim;++i)
if(i<rev[i])
swap(a[i],a[rev[i]]);
for(int len=1;len<lim;len<<=1)
{
ll wn=qp(type==1?G:Gi,(P-1)/(len<<1));
for(int i=0;i<lim;i+=len<<1)
{
ll w=1;
for(int j=i;j<i+len;++j,w=w*wn%P)
{
ll x=a[j],y=w*a[j+len]%P;
a[j]=(x+y)%P,a[j+len]=(x-y+P)%P;
}
}
}
if(type==1) return;
ll inv=qp(lim,P-2);
for(int i=0;i<lim;++i) a[i]=a[i]*inv%P;
}
void mul(ll *f,ll *g)
{
int lim=calc(n+m);
NTT(f,lim,1),NTT(g,lim,1);
for(int i=0;i<lim;++i) f[i]=f[i]*g[i]%P;
NTT(f,lim,-1);
}
多项式求导和积分
多项式牛顿迭代
已知 (f(x)),求 (g(x)),满足 (f(g(x)) equiv 0 pmod{x^n})。
设已经求得 (g_0(x)),满足 (g_0(x) equiv g(x) pmod{x^n}),由泰勒展开得:
得:
多项式求逆
称 (g(x)) 为 (f(x)) 的逆元,模 (x^n) 的意义是将次数大于等于 (n) 的项都忽略掉。
设已经求得满足
的 (g(x)),得:
本质为牛顿迭代
设 (g(x) = f^{-1}(x)),(h(g(x)) = g^{-1}(x) - f(x) =0)。
设已经求得 (g_0(x)),满足 (g_0(x) equiv g(x) pmod{x^n}),得:
多项式对数函数
多项式指数函数
设 (g(x) = e^{f(x)}),(h(g(x)) = ln g(x) - f(x) =0)。
设已经求得 (g_0(x)),满足 (g_0(x) equiv g(x) pmod{x^n}),得:
多项式幂函数
多项式开根
可以直接使用多项式幂函数求解,也可以用牛顿迭代求解。
设 (g^2(x) = f(x)),(h(g(x)) = g^2(x) - f(x) =0)。
设已经求得 (g_0(x)),满足 (g_0(x) equiv g(x) pmod{x^n}),得:
多项式除法
已知 (n) 次多项式 (f(x)) 和 (m) 次多项式 (g(x)),求满足 (f(x) equiv g(x)h(x)+t(x) pmod{x^{n+1}}) 的 (n-m) 次多项式 (h(x)) 和次数小于 (m) 的多项式 (t(x))。
设 (f_r(x)) 为多项式 (f(x)) 系数翻转得到的多项式,得 (f_r(x) = x^nf(x^{-1}))。
求出 (h(x)) 后,得 (t(x) equiv f(x) - g(x)h(x) pmod{x^m})。
递推
多项式操作的 (O(n^2)) 递推。
多项式求逆
边界为 (g_0=inv(f_0))。
多项式对数函数
多项式指数函数
边界为 (g_0=1)。
模板
void Inv(int deg,ll *a,ll *b)
{
static ll t[maxn];
if(deg==1)
{
b[0]=qp(a[0],P-2);
return;
}
Inv((deg+1)>>1,a,b);
int lim=calc(deg<<1);
for(int i=0;i<deg;++i) t[i]=a[i];
for(int i=deg;i<lim;++i) t[i]=b[i]=0;
NTT(t,lim,1),NTT(b,lim,1);
for(int i=0;i<lim;++i) b[i]=b[i]*(2-t[i]*b[i]%P+P)%P;
NTT(b,lim,-1);
for(int i=deg;i<lim;++i) b[i]=0;
}
void Ln(int deg,ll *a,ll *b)
{
static ll inva[maxn],dera[maxn];
Inv(deg,a,inva);
for(int i=0;i<deg-1;++i) dera[i]=a[i+1]*(i+1)%P;
dera[deg-1]=0;
int lim=calc(deg<<1);
for(int i=deg;i<lim;++i) dera[i]=inva[i]=0;
NTT(dera,lim,1),NTT(inva,lim,1);
for(int i=0;i<lim;++i) b[i]=dera[i]*inva[i]%P;
NTT(b,lim,-1);
for(int i=deg-1;i>=1;--i) b[i]=b[i-1]*qp(i,P-2)%P;
b[0]=0;
for(int i=deg;i<lim;++i) b[i]=0;
}
void Exp(int deg,ll *a,ll *b)
{
static ll t[maxn],lnb[maxn];
if(deg==1)
{
b[0]=1;
return;
}
Exp((deg+1)>>1,a,b),Ln(deg,b,lnb);
int lim=calc(deg<<1);
for(int i=0;i<deg;++i) t[i]=(a[i]-lnb[i]+P)%P;
for(int i=deg;i<lim;++i) t[i]=b[i]=0;
NTT(t,lim,1),NTT(b,lim,1);
for(int i=0;i<lim;++i) b[i]=b[i]*(1+t[i])%P;
NTT(b,lim,-1);
for(int i=deg;i<lim;++i) b[i]=0;
}
void Pow(int deg,ll *a,ll *b,ll k)
{
static ll lna[maxn];
Ln(deg,a,lna);
for(int i=0;i<deg;++i) lna[i]=lna[i]*k%P;
Exp(deg,lna,b);
}
void Sqrt(int deg,ll *a,ll *b)
{
static ll t[maxn],invb[maxn];
if(deg==1)
{
b[0]=1;
return;
}
Sqrt((deg+1)>>1,a,b);
int lim=calc(deg<<1);
for(int i=0;i<deg;++i) t[i]=2*b[i]%P,invb[i]=0;
for(int i=deg;i<lim;++i) t[i]=invb[i]=0;
Inv(deg,t,invb);
for(int i=0;i<deg;++i) t[i]=a[i];
for(int i=deg;i<lim;++i) t[i]=b[i]=0;
NTT(t,lim,1),NTT(b,lim,1),NTT(invb,lim,1);
for(int i=0;i<lim;++i) b[i]=(b[i]*b[i]%P+t[i])%P*invb[i]%P;
NTT(b,lim,-1);
for(int i=deg;i<lim;++i) b[i]=0;
}