多 项 式 全 家 桶(便携式)
多项式卷积
用 \(F\times G\) 表示 \(F\) 与 \(G\) 的卷积。暂时略去 FFT 与 NTT 的推导过程(应该……会补的吧?)。时间复杂度 \(O(n\log n)\)。
多项式牛顿迭代
给定 \(F\)。求 \(G\) 使 \(F\circ G\equiv 0\pmod {x^n}\)。这里 \(F\circ G(x)=F(G(x))\)。
考虑倍增。\(n=1\) 时单独求解。假设已经得到了 \(\bmod m_1=x^{\lceil\frac{n}{2}\rceil}\) 的解 \(G_1\),考虑 \(\bmod m=x^{n}\)。
将 \(F\) 在 \(G_1(x)\) 处进行泰勒展开,有
因为 \(G(x)-G_1(x)\) 的最低非零项次数为 \(\lceil\frac{n}{2}\rceil\),所以 \(i\ge 2\) 时的项恒为 \(0\)。因此
一般来说不会直接用到,但是可以用它直接解决下面的几个问题。
多项式乘法逆
给定 \(F\),求 \(G\) 使 \(F\times G\equiv 1\pmod{x^{n}}\)。\(G\) 可以记作 \(\frac{1}{F}\) 或 \(F^{-1}\)。
应用牛顿迭代。方程是 \(\frac{1}{G}-F=0\)。有 \(G=G_1-\frac{G_1^{-1}-F}{-G_1^{-2}}=2G_1-G_1^2F=G_1(2-G_1F)\)。时间复杂度 \(T(n)=T(\frac{n}{2})+O(n\log n)=O(n\log n)\)。
多项式带余除法
给定 \(F,G\),求 \(Q,R\) 满足 \(F=G\times Q+R,\deg R\lt \deg G\)。可以证明 \((Q,R)\) 是唯一存在的。
记 \(F_r\) 表示将 \(F\) 的系数序列翻转之后的多项式,\(G_r,Q_r,R_r\) 同理。记 \(n=\deg F,m=\deg G\)。考虑将 \(\frac{1}{x}\) 作为自变量代入式子,则得到 \(F_r=G_r\times G_r+R\times x^{n-m+1}\)。式子两边对 \(x^{n-m+1}\) 取模得到 \(Q_r\equiv \frac{F_r}{G_r}\pmod {x^{n-m+1}}\)。而 \(\deg Q=n-m\),所以直接用多项式乘法计算 \(\frac{F_r}{G_r}\) 就可以得到 \(Q_r\)。有了 \(Q_r\) 自然有 \(Q\),那么 \(R=F-G\times Q\) 即可。时间复杂度 \(O(n\log n)\)。
多项式开方
给定 \(F\),求 \(G\) 使 \(G^2\equiv F\pmod {x^n}\)。
应用牛顿迭代。有 \(G=G_1-\frac{G_1^2-F}{2G_1}=\frac{F+G_1^2}{2G_1}\)。时间复杂度 \(O(n\log n)\)。
多项式求导 / 不定积分
太简单了。直接按照定义做就可以了。时间复杂度 \(O(n)\)。
多项式 ln
给定 \(F\),求 \(G\) 使 \(G\equiv \ln F\pmod {x^n}\)。注意只有当 \(F(0)=1\) 时有解。
\(\ln\) 没法处理,考虑两边求导。有 \(G'\equiv \frac{F'}{F} \pmod {x^n}\)。于是 \(G\equiv \int {\frac{F'}{F}} \pmod {x^n}\)。
多项式 exp
给定 \(F\),求 \(G\) 使 \(G\equiv e^{F} \pmod {x^n}\)。注意只有当 \(F(0)=0\) 时有解。
应用牛顿迭代。有 \(G=G_1-\frac{\ln G_1-F}{\frac{1}{G_1}}=G_1(1-\ln G_1+F)\)。时间复杂度 \(O(n\log n)\)。
多项式快速幂
给定 \(F,k\),求 \(F^k\bmod x^n\)。
快速幂使用倍增?大可不必。直接做 \(e^{k\cdot \ln F}\) 即可。如果 \(F(0)\neq 1\) 则只要用 \(\frac{F}{F(0)}\) 计算,最后乘上 \(F(0)^{k}\)。时间复杂度 \(O(n\log n)\)。注意可能需要特判 \(F(0)=0\) 的情形。
其实直接使用倍增当然也是可以的,还能够处理任意的模多项式。
const int N=2e5+5,P=998244353;
int qpow(int a,int b=P-2){
int c=1;
for(;b;b>>=1,a=1ll*a*a%P)
if(b&1)c=1ll*c*a%P;
return c;
}
typedef vector<int> arr;
namespace poly{
const int _G=3,invG=::qpow(3);
int tr[N<<1];
void NTT(arr&f,int op){
int n=f.size();
for(int i=0;i<n;++i)
if(i<tr[i])swap(f[i],f[tr[i]]);
for(int p=2;p<=n;p<<=1){
int len=p>>1,tG=::qpow(op==1?_G:invG,(P-1)/p);
for(int k=0;k<n;k+=p){
int buf=1;
for(int l=k;l<k+len;++l){
int tmp=1ll*buf*f[len+l]%P;
f[len+l]=(f[l]-tmp+P)%P;f[l]=(f[l]+tmp)%P;
buf=1ll*buf*tG%P;
}
}
}
if(op==-1){
int inv=::qpow(n);
for(int i=0;i<n;++i)f[i]=1ll*f[i]*inv%P;
}
}//NTT模板
arr times(arr f,arr g){
int n=f.size(),m=g.size();
for(m+=n,n=1;n<=m;n<<=1);
f.resize(n);g.resize(n);
for(int i=0;i<n;++i)
tr[i]=(tr[i>>1]>>1)|((i&1)?n>>1:0);
NTT(f,1);NTT(g,1);
for(int i=0;i<n;++i)f[i]=1ll*f[i]*g[i]%P;
NTT(f,-1);return f;
}//多项式卷积
arr inv(int n,arr f){
if(n==1)return {::qpow(f[0])};
arr g=inv((n+1)/2,f);f.resize(n);
f=times(f,g);for(int i=0;i<n;i++)f[i]=P-f[i];f[0]+=2;
f.resize(n);f=times(f,g);f.resize(n);return f;
}//多项式求逆
arr sqrt(int n,arr f){
if(n==1)return {1};//这里只考虑f[0]=1的情况。实际上可能要求二次剩余。
arr g1=sqrt((n+1)/2,f);f.resize(n);
arr g=times(g1,g1);g.resize(n);int inv2=(P+1)/2;
for(int i=0;i<n;i++)g[i]=1ll*(f[i]+g[i])%P*inv2%P;
g1.resize(n);g1=inv(n,g1);g=times(g,g1);g.resize(n);
return g;
}//多项式开根
pair<arr,arr> mod(arr f,arr g){
int n=f.size(),m=g.size();arr ff=f,gg=g;
reverse(ff.begin(),ff.end());
reverse(gg.begin(),gg.end());gg.resize(n+1);
arr q=times(ff,inv(n+1,gg));
q.resize(n-m+1);reverse(q.begin(),q.end());
arr r=times(g,q);r.resize(m-1);
for(int i=0;i<m-1;i++)r[i]=(f[i]-r[i]+P)%P;
return make_pair(q,r);
}//多项式带余除法
arr ln(arr f){
int n=f.size();arr f1;f1.resize(n);
for(int i=1;i<n;i++)f1[i-1]=1ll*f[i]*i%P;
f=inv(n,f);f1=times(f1,f);f1.resize(n);
f[0]=0;for(int i=1;i<n;i++)f[i]=1ll*f1[i-1]*::qpow(i)%P;
return f;
}//多项式 ln
arr exp(int n,arr f){
if(n==1)return {1};
arr g1=exp((n+1)/2,f);f.resize(n);g1.resize(n);
arr g=ln(g1);for(int i=0;i<n;i++)g[i]=(f[i]-g[i]+P)%P;++g[0];
g=times(g,g1);g.resize(n);return g;
}//多项式 exp
arr qpow(arr f,int k1,int k2){
//因为k可能很大,所以传入k1=k%P,k2=k%(P-1)
int n=f.size(),x=f[0],invx=::qpow(x);
for(int i=0;i<n;i++)f[i]=1ll*f[i]*invx%P;
f=ln(f);for(int i=0;i<n;i++)f[i]=1ll*f[i]*k1%P;
f=exp(n,f);x=::qpow(x,k2);
for(int i=0;i<n;i++)f[i]=1ll*f[i]*x%P;
return f;
}//多项式快速幂
}