多项式
形如 \(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(n\log 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 \bmod 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;
}