多项式基础运算
前置知识:NTT
多项式乘法逆
加减乘都完成了,我们还差一个除:
数字在取模运算中的乘法,我们通常用求逆元来完成,所以我们先来看看多项式的乘法逆:
考虑递归求解,我们要求的是\(F(x)\)满足
假设我们已经求出了\(F_0(x)\)满足
又因为
故
两边平方得:
左右两边同成\(G(x)\)得:
因为
所以
然后就可以递归了,边界就是\(F(x)\)只有一项时,\(G(x)\)就是这一项的逆元。
这里只给出了,求逆部分的代码,容器及\(NTT\)请参见我的上一篇博客
inline poly getinv(int n,poly f){
if(n==1){poly g;g[0]=ksm(f[0],mod-2);return g;}
int t=(n+1)>>1;
poly g=getinv(t,f);poly p=g;
int lim=1,len=0;
while(lim<(n<<1)) lim<<=1,len++;
for(int i=0;i<lim;++i) r[i]=(r[i>>1]>>1)|((i&1)<<(len-1));
if(f.size()>n) f.mem(n,min(lim,f.size())-1,0);
if(p.size()>n) p.mem(n,min(lim,g.size())-1,0);
NTT(lim,f,1);NTT(lim,p,1);
f=f*p*p;
NTT(lim,f,-1);
int iv=ksm(lim,mod-2);
poly h;
for(int i=0;i<n;++i) h[i]=dec(2ll*g[i]%mod,1ll*f[i]*iv%mod);
return h;
}
多项式对数函数(多项式ln)
考虑求的是:
对两边求导:
这里用到了复合函数的求导法则:
于是对\(A(x)\)求逆并求导,得到\(B'(x)\)后积分回去即可。
根据求导的基本公式\((x^n)'=nx^{n-1}\),于是扫一遍就可以完成多项式的求导与积分(忽略我的函数名):
int inv[N],tp=2;
inline void init(int n){
if(tp==2) inv[0]=inv[1]=1;
for(tp;tp<=n;++tp)
inv[tp]=1ll*(mod-mod/tp)*inv[mod%tp]%mod;
}
inline void getdao(int n,poly& f){
for(int i=1;i<n;++i) f[i-1]=1ll*i*f[i]%mod;
f[n-1]=0;
}
inline void jifen(int n,poly& f){
init(n);
for(int i=n-1;i;--i) f[i]=1ll*inv[i]*f[i-1]%mod;
f[0]=0;
}
求\(\ln\)的代码就很简单了:
inline poly getln(int n,poly f){
poly g=getinv(n,f);getdao(n,f);
poly h=poly_mul(n,n,f,g);
jifen(n,h);
return h;
}
多项式除法
一般的除法,我们直接通过乘其乘法逆来解决,但有些时候,我们不仅要除,还要取模,这该怎么办呢:
我们已知\(n\)次多项式\(F(x)\)与\(m\)次多项式\(G(x)\),要求\(n-m\)次多项式\(Q(x)\)和小于\(m\)次的多项式\(R(x)\)满足
我们先引入一种新的操作,对于\(n\)次多项式(注意\(n\)次多项式是最高次数为\(n\)的多项式,但在我的所有代码与题解中,我使用的\(n\)都表示最高次数为\(n-1\)):
展开后可以发现:
没错,这一操作就相当于翻转了\(F(x)\) 的系数,可以\(O(n)\)完成。
利用这个操作,我们去推柿子:
因为\(x^{n-m+1}R_R(x)\)的前\(n-m+1\)项系数一定为\(0\),于是
求一遍\(G(x)\)的逆,我们就算出\(Q_R(x)\),进而求出\(Q\)了。
至于\(R(x)\),我们直接由
求出
以上是多项式比较基础的几个运算,更高阶的一些运算(如开根,\(\exp\)等)需要用到更高阶的知识——多项式牛顿迭代