多项式半家桶,但是未封装
多项式乘法逆
题意:
给定 \(n-1\) 次多项式 \(F(x)\) ,求多项式 \(G(x)\),使得 \(F(x) G(x) \equiv 1 \pmod {x^n}\)
思路:
设 :
已知 \(g(x)\) ,考虑如何求解 \(G(x)\) 。
因为:
因此有:
两者相减,于是有:
再平方(注意此时模数变为了原来的二倍):
左右两边同乘 \(F(x)\) :
将 \(g_0\) 要初始化成 \(F_0\) 的乘法逆元,递推或者递归求解 \(G(x)\) 。
注意事项:
-
边界问题,即 \(F(x)\) 应该取到 \(2m-1\) 次方, \(g(x)\) 应该取到 \(m-1\) 次方,求得的 \(G(x)\) 应该取到 \(2m-1\) 次方(超出的设为 \(0\)) ,进行多项式乘法时至少要使长度 \(lim > 4m\)
-
递归求解时要先求 \(\bmod \lceil \frac{n}{2}\rceil\) 意义下的 \(g(x)\) ,再求 \(\bmod n\) 意义下的 \(G(x)\)
多项式开根
题意:
给定 \(n-1\) 次多项式 \(F(x)\) ,求多项式 \(G(x)\),使得 \(G(x)^2 \equiv F(x) \pmod {x^n}\) 。
保证 \(F_0=1\) 。
思路:
延续着多项式求逆倍增的思路,我们依然设:
已知 \(g(x)\) ,考虑如何求解 \(G(x)\) 。
因为:
因此有:
两边平方:
用 \(F(x)\) 替换 \(G(x)^2\) :
初始化 \(g_0=1\),递推或递归求解 \(G(x)\) 。
注意事项跟多项式求逆差不多。
但如果不封装的话,注意求逆时数组混用的问题。
当 \(F_0 \ne 1\) 时,需要求解 \(F_0\) 在 \(\bmod p\) 意义下的二次剩余作为初始值。
多项式求导
给定 \(n-1\) 次多项式 \(F(x)\) ,求 \(F'(x) \pmod {x^n}\) 。
因为
所以 :
code
inline void Diff(ll *a,int len){
for(Reg int i=0;i<len-1;++i) a[i]=a[i+1]*(i+1)%mod;
a[len-1]=0;
}
多项式积分
给定 \(n-1\) 次多项式 \(F(x)\) ,求 \(F(x)\) 的不定积分 \(G(x)\) 。
因为:
所以
code
inline void Inte(ll *a,int len){
for(Reg int i=len-1;i>0;--i) a[i]=a[i-1]*qpow(i,mod-2)%mod;
a[0]=0;
}
多项式对数函数 (ln)
题意:
给定 \(n-1\) 次多项式 \(F(x)\) ,求多项式 \(G(x)\),使得 \(G(x) \equiv \ln {F(x)} \pmod {x^n}\) 。
保证 \(F_0=1\) 。
思路:
考虑同时对两边求导,有:
再同时对两边做不定积分:
对 \(F(x)\) 求导和求乘法逆,相乘再积分即可求得 \(G(x)\) 。
多项式指数函数 (exp)
题意:
给定 \(n-1\) 次多项式 \(F(x)\) ,求多项式 \(G(x)\),使得 \(G(x) \equiv e^{F(x)} \pmod {x^n}\) 。
保证 \(F_0=0\) 。
思路:
不会牛顿迭代,麻。
对两边求导:
用 \(G(x)\) 将 \(e^{F(x)}\) 替换:
对两边做不定积分:
设 \(T(x)=F'(x)G(x)\) ,那么对于 \(G(x)\) 的第 \(i\) 项系数 \(G_i\) 有:
也就是:
然后就可以半在线卷积了,复杂度 \(O(n\log ^2 n)\) 。
留坑待补。
好了来补了。
泰勒展开
设 \(f^{(i)}(x)\) 为 \(f(x)\) 的 \(i\) 阶导,定义函数 \(f(x)\) 在 \(x_0\)的泰勒展开式如下:
可以理解为通过多项式函数 \(F(x)\) 在 \(x_0\) 处去模仿 \(f(x)\) 的导数,以无限逼近 \(f(x)\) 。
牛顿迭代法
牛顿迭代法可以求出单调函数零点的近似解。
对于一个函数 \(f(x)\) ,已知其在 \([a,b]\) 上连续且单调。 设 \(x_0\) 为一个 \(f(x)=0\) 的近似解。
假设从 \(x_i\) 开始,我们每次在 \(f(x_i)\) 的位置作一个关于 \(f(x)\) 的切线,显然,切线与 \(x\) 轴相交的位置则是一个比 \(x_i\) 更靠近零点的位置 \(x_{i+1}\) 。根据导数的几何意义有:
整理一下可得:
多项式牛顿迭代
若 \(G(f(x)) = 0 \pmod {x^m} ,G(F(x)) =0 \pmod {x^{2m}}\) ,则有 $F(x) \equiv f(x)-\frac{G(f(x))}{G'(f(x))} \pmod {x^{2m}} $
类比普通函数应该可以感性理解(
不那么感性的证明:
首先应该有 $F(x)\equiv f(x) \pmod {x^{m}} $ 。
我们有 \(G(F(x))\) 在 \(f(x)\) 处的泰勒展开式为:
\(F(x)\) 与 \(f(x)\) 作差之后只会保留后 \(m\) 项,也就是最低次数为 \(m\) ,而 \((F(x)-f(x))^2\) 以后的最低次数至少为 \(2m\) ,在模 \(x^{2m}\) 意义下与 \(0\) 同余,于是:
用 \(0\) 替换 \(G(F(x))\),整理后的式子即为 :
可以通过倍增求解。
回归题意,我们要求的是:
两边取一下 \(\ln\) 有:
设 \(H(G(x)) = \ln G(x) -F(x) \pmod {x^n}\) ,要求的就是 \(H(x)\) 的零点,使用牛顿迭代法:
倍增过程中进行多项式 \(\ln\) 和多项式乘法即可。
复杂度为 \(O(n\log n)\) ,但是常数比半在线卷积大。
多项式快速幂
题意:
给定 \(n-1\) 次多项式 \(F(x)\) 与整数 \(k\) 。求多项式 \(G(x)\),使得 \(G(x) \equiv \ {F(x)}^k \pmod {x^n}\) 。
其中 $ 0\le k\le 10^{100000}, F_0=1 $
思路:
首先想的应该是对指数取模,直接模 \(mod\) 即可。
这时好像可以写倍增,但可以有复杂度更优的做法,考虑转化题意。
对两边取自然对数:
然后再取指数:
于是一次 \(\ln\) 一次 \(\exp\) 就做完了。
当 $F_0=1 $ 时, $ \ln F_0=0$ ,所以 \(\exp\) 时 $G_0 $ 应该设为 \(1\) 。
当 \(F_0\ne 1\) 时的 \(G_0\) 应为 $F_0^k \bmod mod $ ,特别注意 \(F_0=0\) 时没有 \(\ln F_0\) (不存在),因此要找到一个系数为 \(0\) 的前缀长度 \(len\) ,乘上 \(k\) 即为 \(G(x)\) 前缀 为 \(0\) 的长度。
代码
code
#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define Reg register
#define ll long long
using namespace std;
const int maxn=411000;
const int G=3,mod=998244353;
int n,p,R[maxn];
ll A[maxn],B[maxn],C[maxn],D[maxn],E[maxn];
ll f[maxn],g[maxn];
inline ll read(int typ){
ll s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
if(typ) s=(s*10%mod+(ch^48))%mod;
else s=(s<<1)+(s<<3)+(ch^48);
ch=getchar();
}
return s*w;
}
inline ll qpow(ll A,ll B){
ll Ans=1;
while(B){
if(B&1) Ans=Ans*A%mod;
A=A*A%mod;
B>>=1;
}
return Ans;
}
inline ll upd(ll A,ll B){
A+=B;
return A>=mod?A-mod:A;
}
inline ll dow(ll A,ll B){
A-=B;
return A<0?A+mod:A;
}
inline void Getr(int len,int L){
for(Reg int i=0;i<len;++i) R[i]=(R[i>>1]>>1)|((i&1)<<(L-1));
}
inline void NTT(ll *a,int lim,ll typ){
for(Reg int i=0;i<lim;++i){
if(i<R[i]) swap(a[i],a[R[i]]);
}
for(Reg int j=1,p=2;j<lim;j<<=1,p<<=1){
ll t=qpow(G,(typ*((mod-1)/p)+mod-1)%(mod-1));
for(Reg int k=0;k<lim;k+=p){
ll T=1;
for(Reg int l=0;l<j;++l,T=T*t%mod){
ll Nx=a[k+l],Ny=T*a[k+l+j]%mod;
a[k+l]=upd(Nx,Ny);
a[k+l+j]=dow(Nx,Ny);
}
}
}
if(typ==-1){
ll r=qpow(lim,mod-2);
for(Reg int i=0;i<lim;++i) a[i]=a[i]*r%mod;
}
}
inline void Diff(ll *a,int len){
for(Reg int i=0;i<n-1;++i) a[i]=a[i+1]*(i+1)%mod;
a[len-1]=0;
}
inline void Inte(ll *a,int len){
for(Reg int i=n-1;i>0;--i) a[i]=a[i-1]*qpow(i,mod-2)%mod;
a[0]=0;
}
inline void Getinv(ll *a,ll *b,int len){
b[0]=qpow(a[0],mod-2),void();
int lim=1,L=0,lstlim=0;
while(lim<2*len){
lstlim=lim;
lim<<=1,++L,Getr(lim,L);
memset(D,0,sizeof(ll)*(lim+10));
memset(E,0,sizeof(ll)*(lim+10));
for(Reg int i=0;i<lstlim;++i) D[i]=a[i],E[i]=b[i];
NTT(D,lim,1),NTT(E,lim,1);
for(Reg int i=0;i<lim;++i) D[i]=(2-D[i]*E[i]%mod+mod)%mod*E[i]%mod;
NTT(D,lim,-1);
for(Reg int i=0;i<lstlim;++i) b[i]=D[i];
}
}
inline void Getsqrt(ll *a,ll *b,int len){
b[0]=1;
int lim=1,L=0,lstlim=0;
while(lim<2*len){
lstlim=lim;
lim<<=1,++L,Getr(lim,L);
memset(A,0,sizeof(ll)*(lim+10));
memset(B,0,sizeof(ll)*(lim+10));
memset(C,0,sizeof(ll)*(lim+10));
for(Reg int i=0;i<lstlim;++i) A[i]=a[i],B[i]=b[i];
NTT(A,lim,1),NTT(B,lim,1);
for(Reg int i=0;i<lstlim;++i) b[i]=2*b[i]%mod;
Getinv(b,C,lstlim);
NTT(C,lim,1);
for(Reg int i=0;i<lim;++i) A[i]=(A[i]+B[i]*B[i]%mod+mod)%mod*C[i]%mod;
NTT(A,lim,-1);
for(Reg int i=0;i<lstlim;++i) b[i]=A[i];
}
}
inline void Ln(ll *a,ll *b,int len){
Getinv(a,C,len),Diff(a,len);
int lim=1,L=0;
while(lim<=2*len) lim<<=1,++L;Getr(lim,L);
NTT(a,lim,1),NTT(C,lim,1);
for(Reg int i=0;i<lim;++i) a[i]=a[i]*C[i]%mod;
NTT(a,lim,-1);
for(Reg int i=0;i<len;++i) b[i]=a[i];
Inte(b,len);
}
inline void calc(ll *a,ll *b,int lim){
NTT(a,lim,1),NTT(b,lim,1);
for(Reg int i=0;i<lim;++i) a[i]=a[i]*b[i]%mod;
NTT(a,lim,-1);
}
inline void Solve(ll *a,ll *b,int l,int r){
if(l==r){
//printf("%lld\n",f[l]);
if(l) a[l]=a[l]*qpow(l,mod-2)%mod;
else a[l]=1;
return;
}
int mid=(l+r)>>1;
Solve(a,b,l,mid);
int up=r-l+1,lim=1,L=0;
while(lim<=2*up) lim<<=1,++L;Getr(lim,L);
memset(A,0,sizeof(ll)*(lim+10));
memset(B,0,sizeof(ll)*(lim+10));
for(Reg int i=0;i<mid-l+1;++i) A[i]=a[i+l];
for(Reg int i=1;i<=r-l;++i) B[i]=b[i-1];
calc(A,B,lim);
for(Reg int i=mid-l+1;i<=r-l;++i) a[i+l]=(a[i+l]+A[i])%mod;
Solve(a,b,mid+1,r);
}
inline void Exp(ll *a,ll *b,int len){
Diff(a,len);
Solve(b,a,0,len-1);
}
/*
inline void Exp(ll *a,ll *b,int len){
b[0]=1;
int lim=1,L=0;
while(lim<=2*len){
lim<<=1,++L;
memset(A,0,sizeof(ll)*(lim+10));
memset(B,0,sizeof(ll)*(lim+10));
memset(H,0,sizeof(ll)*(lim+10));
for(Reg int i=0;i<(lim>>1);++i) A[i]=a[i],B[i]=b[i];
Ln(b,H,(lim>>1)),Getr(lim,L);
NTT(A,lim,1),NTT(B,lim,1),NTT(H,lim,1);
for(Reg int i=0;i<lim;++i) A[i]=B[i]*((1-H[i]+mod+A[i])%mod)%mod;
NTT(A,lim,-1);
for(Reg int i=0;i<(lim>>1);++i) b[i]=A[i];
}
}
*/
inline void Polyqpow(ll *a,ll *b,ll p,int len){
Ln(a,a,len);
for(Reg int i=0;i<len;++i) a[i]=a[i]*p%mod;
Exp(a,b,len);
}
int main(){
n=read(0);//p=read(1);
for(Reg int i=0;i<n;++i) f[i]=read(0);
//Getinv(f,g,n),Getsqrt(f,g,n),Ln(f,g,n),Exp(f,g,n),Polyqpow(f,g,p,n);
for(Reg int i=0;i<n;++i) printf("%lld ",g[i]);
printf("\n");
return 0;
}