Formal Power Series Composition & Compositional Inverse
Composition and compositional inverse of formal power series are common operations. For general \(f\) without special properties, we have mostly used \(O\left(n^2\right)\) algorithms to compute \(f(g) \bmod{x^n}\), where \(f\in\mathbb{C}\left\lbrack\left\lbrack x\right\rbrack\right\rbrack\) and \(g\in x\mathbb{C}\left\lbrack\left\lbrack x\right\rbrack\right\rbrack\). However, due to low efficiency, their applications are limited. We introduce the Kinoshita–Li algorithm with complexity \(O\left(\mathsf{M}\left(n\right)\log n\right)\), where \(O\left(\mathsf{M}\left(n\right)\right)\) is the time for multiplying two polynomials of degree \(O\left( n\right)\).
Composition of Formal Power Series/Polynomials
To compute \(f\left(g\left(x\right)\right)\bmod{x^n}\), each coefficient of \(f\left(g\left(x\right)\right)\) must be a finite sum. This previously required \(f(x)\in\mathbb{C}\left\lbrack\left\lbrack x\right\rbrack\right\rbrack\) and \(g(x)\in x\mathbb{C}\left\lbrack\left\lbrack x\right\rbrack\right\rbrack\). However, if \(f(x),g(x)\in\mathbb{C}\left\lbrack x\right\rbrack\), this condition is also satisfied. Since we need to truncate the coefficients of \(f\left(g\left(x\right)\right)\), we can directly consider the case where both \(f(x)\) and \(g(x)\) are polynomials. For \(f(x)=\sum_{j=0}^{n-1}f_jx^j\), we have
When computing \(\dfrac{P(y)}{V(z,y)}\bmod{z^{\left\lceil n/2\right\rceil}}\in\mathbb{C}\left\lbrack z\right\rbrack\left(\left( y\right)\right)\), we do not need to keep all coefficients of \(y\), because we only need to extract the coefficient of \(y^0\) at the end. Since after obtaining the former, we need to multiply it by several polynomials of the form \(Q(-x,y)\in\mathbb{C}\left\lbrack x,y\right\rbrack\), it suffices to keep only the coefficients contributing to \(y^0\). We are ready to present the pseudocode:
Note the third parameter: because \(g(0)\) may be non-zero, if \(\deg f\geq n\), we cannot truncate \(f(x)\) to compute \(f\left(g(x)\right)\). Alternatively, we can compute \(f(g)=f\circ \left(x+g(0)\right)\circ \left(g-g(0)\right)\). In this case, we can take \(F:=f\left(x+g(0)\right)\bmod{x^n}\) and \(G:=g-g(0)\), and then compute \(\operatorname{\mathsf{Comp}}\left(F\left(y^{-1}\right),1-y\cdot G(x),n,1\right)\).
Additionally, due to the constraints of the recursion, \(Q(0,y)^{-1}\) at the termination can be derived directly without using the formal power series multiplicative inverse algorithm. We only need to compute one multiplication and extract the required coefficients.
The power function is also used in computing the compositional inverse.
Kronecker Substitution
Before analyzing time complexity, consider how to perform bivariate polynomial multiplication. One idea is to "pack" the coefficients. This method, introduced by Kronecker in 1882, reduces multiplication in \(R\left\lbrack x,y\right\rbrack\) to multiplication in \(R\left\lbrack x\right\rbrack\) via the substitution \(y\mapsto x^N\), requiring \(N\) to be sufficiently large.
Assume \(\deg_x \left(AB\right)<N\). Then after computing \(A\left(x,x^N\right)B\left(x,x^N\right)\), we can recover \(A(x,y)B(x,y)\), with "packing" and "unpacking" taking linear time.
Using Kronecker substitution and then computing univariate polynomial multiplication, it is not difficult to see that when \(n\) is a power of two, the above algorithm can be completed in \(O\left(\mathsf{M}\left(n\right)\log n\right)\) time, because in each recursion, the degree in \(y\) doubles while the degree in \(x\) halves.
```cpp#include<cassert>#include<cstdio>#include<tuple>#include<utility>#include<vector>usinguint=unsigned;usingull=unsignedlonglong;constexpruintMOD=998244353;constexpruintPowMod(uinta,ulle){for(uintres=1;;a=(ull)a*a%MOD){if(e&1)res=(ull)res*a%MOD;if((e/=2)==0)returnres;}}constexpruintInvMod(uinta){returnPowMod(a,MOD-2);}constexpruintQUAD_NONRESIDUE=3;constexprintLOG2_ORD=23;// __builtin_ctz(MOD - 1)constexpruintZETA=PowMod(QUAD_NONRESIDUE,(MOD-1)>>LOG2_ORD);constexpruintINV_ZETA=InvMod(ZETA);// Returns the root of unity arrays needed for an n-length FFT, each of half lengthstd::pair<std::vector<uint>,std::vector<uint>>GetFFTRoot(intn){assert((n&(n-1))==0);if(n/2==0)return{};std::vector<uint>root(n/2),inv_root(n/2);root[0]=inv_root[0]=1;for(inti=0;(1<<i)<n/2;++i)root[1<<i]=PowMod(ZETA,1LL<<(LOG2_ORD-i-2)),inv_root[1<<i]=PowMod(INV_ZETA,1LL<<(LOG2_ORD-i-2));for(inti=1;i<n/2;++i)root[i]=(ull)root[i-(i&(i-1))]*root[i&(i-1)]%MOD,inv_root[i]=(ull)inv_root[i-(i&(i-1))]*inv_root[i&(i-1)]%MOD;return{root,inv_root};}voidButterfly(intn,uinta[],constuintroot[]){assert((n&(n-1))==0);for(inti=n;i>=2;i/=2)for(intj=0;j<n;j+=i)for(intk=j;k<j+i/2;++k){constuintu=a[k];a[k+i/2]=(ull)a[k+i/2]*root[j/i]%MOD;if((a[k]+=a[k+i/2])>=MOD)a[k]-=MOD;if((a[k+i/2]=u+MOD-a[k+i/2])>=MOD)a[k+i/2]-=MOD;}}voidInvButterfly(intn,uinta[],constuintroot[]){assert((n&(n-1))==0);for(inti=2;i<=n;i*=2)for(intj=0;j<n;j+=i)for(intk=j;k<j+i/2;++k){constuintu=a[k];if((a[k]+=a[k+i/2])>=MOD)a[k]-=MOD;a[k+i/2]=(ull)(u+MOD-a[k+i/2])*root[j/i]%MOD;}}voidFFT(intn,uinta[],constuintroot[]){Butterfly(n,a,root);}voidInvFFT(intn,uinta[],constuintroot[]){InvButterfly(n,a,root);constuintinv_n=InvMod(n);for(inti=0;i<n;++i)a[i]=(ull)a[i]*inv_n%MOD;}// Formal power series composition, compute f(g) mod x^n, requires g(0) = 0std::vector<uint>FPSComposition(std::vector<uint>f,std::vector<uint>g,intn){assert(g.empty()||g[0]==0);intlen=1;while(len<n)len*=2;std::vector<uint>root,inv_root;std::tie(root,inv_root)=GetFFTRoot(len*4);// [y^(-1)] (f(y) / (-g(x) + y)) mod x^n in R[x]((y^(-1)))constautoKinoshitaLi=[&](auto&&KinoshitaLi,conststd::vector<uint>&P,conststd::vector<uint>&Q,intd,intn){assert((int)P.size()==d*n);assert((int)Q.size()==d*n);if(n==1)returnP;std::vector<uint>dftQ(d*n*4);for(inti=0;i<d;++i)for(intj=0;j<n;++j)dftQ[i*n*2+j]=Q[i*n+j];dftQ[d*n*2]=1;FFT(d*n*4,dftQ.data(),root.data());std::vector<uint>V(d*n*2);for(inti=0;i<d*n*4;i+=2)V[i/2]=(ull)dftQ[i]*dftQ[i+1]%MOD;InvFFT(d*n*2,V.data(),inv_root.data());assert(V[0]==1);V[0]=0;for(inti=1;i<d*2;++i)for(intj=0;j<n/2;++j)V[i*(n/2)+j]=V[i*n+j];V.resize(d*n);conststd::vector<uint>T=KinoshitaLi(KinoshitaLi,P,V,d*2,n/2);std::vector<uint>dftT(d*n*2);for(inti=0;i<d*2;++i)for(intj=0;j<n/2;++j)dftT[i*n+j]=T[i*(n/2)+j];FFT(d*n*2,dftT.data(),root.data());for(inti=0;i<d*n*4;i+=2){constuintu=dftQ[i];dftQ[i]=(ull)dftT[i/2]*dftQ[i+1]%MOD;dftQ[i+1]=(ull)dftT[i/2]*u%MOD;}InvFFT(d*n*4,dftQ.data(),inv_root.data());for(inti=0;i<d;++i)for(intj=0;j<n;++j)dftQ[i*n+j]=dftQ[(i+d)*(n*2)+j];dftQ.resize(d*n);returndftQ;};f.resize(len);g.resize(len);for(inti=0;i<len;++i)g[i]=(g[i]!=0?MOD-g[i]:0);std::vector<uint>res=KinoshitaLi(KinoshitaLi,f,g,1,len);res.resize(n);returnres;}std::pair<std::vector<uint>,std::vector<uint>>GetFactorial(intn){if(n==0)return{};std::vector<uint>factorial(n),inv_factorial(n);factorial[0]=inv_factorial[0]=1;if(n==1)return{factorial,inv_factorial};std::vector<uint>inv(n);inv[1]=1;for(inti=2;i<n;++i)inv[i]=(ull)(MOD-MOD/i)*inv[MOD%i]%MOD;for(inti=1;i<n;++i)factorial[i]=(ull)factorial[i-1]*i%MOD,inv_factorial[i]=(ull)inv_factorial[i-1]*inv[i]%MOD;return{factorial,inv_factorial};}// f(x) |-> f(x + c)std::vector<uint>TaylorShift(std::vector<uint>f,uintc){if(f.empty()||c==0)returnf;constintn=f.size();std::vector<uint>factorial,inv_factorial;std::tie(factorial,inv_factorial)=GetFactorial(n);for(inti=0;i<n;++i)f[i]=(ull)f[i]*factorial[i]%MOD;std::vector<uint>g(n);uintcp=1;for(inti=0;i<n;++i)g[i]=(ull)cp*inv_factorial[i]%MOD,cp=(ull)cp*c%MOD;intlen=1;while(len<n*2-1)len*=2;std::vector<uint>root,inv_root;std::tie(root,inv_root)=GetFFTRoot(len);f.resize(len);g.resize(len);FFT(len,f.data(),inv_root.data());FFT(len,g.data(),root.data());for(inti=0;i<len;++i)f[i]=(ull)f[i]*g[i]%MOD;InvFFT(len,f.data(),root.data());f.resize(n);for(inti=0;i<n;++i)f[i]=(ull)f[i]*inv_factorial[i]%MOD;returnf;}// Polynomial composition, compute f(g) mod x^nstd::vector<uint>PolyComposition(conststd::vector<uint>&f,conststd::vector<uint>&g,intn){if(g.empty()||g[0]==0)returnFPSComposition(f,g,n);std::vector<uint>gg=g;gg[0]=0;returnFPSComposition(TaylorShift(f,g[0]),gg,n);}intmain(){intn,m;std::scanf("%d%d",&n,&m);std::vector<uint>f(n+1),g(m+1);for(inti=0;i<=n;++i)std::scanf("%u",&f[i]);for(inti=0;i<=m;++i)std::scanf("%u",&g[i]);conststd::vector<uint>res=PolyComposition(f,g,n+1);for(inti=0;i<=n;++i)std::printf("%u%c",res[i]," \n"[i==n]);return0;}
```
Compositional Inverse of Formal Power Series
Given \(f\in x\mathbb{C}\left\lbrack\left\lbrack x\right\rbrack\right\rbrack\) with \(f'(0)\neq 0\), find \(g(x)\bmod{x^n}\) such that \(f(g)\equiv g(f)\equiv x\pmod{x^n}\).
\[
\begin{aligned}
u&=\begin{cases}
\left\lbrack x^{n-1}\right\rbrack\dfrac{U_e\left(x^2,y\right)}{V\left(x^2,y\right)}&\text{ if }n-1\text{ is even,} \\
\left\lbrack x^{n-1}\right\rbrack\dfrac{xU_o\left(x^2,y\right)}{V\left(x^2,y\right)}&\text{ if }n-1\text{ is odd.}
\end{cases} \\
&=\begin{cases}
\left\lbrack x^{\left\lceil n/2\right\rceil-1}\right\rbrack\dfrac{U_e\left(x,y\right)}{V\left(x,y\right)}&\text{ if }n-1\text{ is even,} \\
\left\lbrack x^{\left\lceil n/2\right\rceil-1}\right\rbrack\dfrac{U_o\left(x,y\right)}{V\left(x,y\right)}&\text{ if }n-1\text{ is odd.}
\end{cases}
\end{aligned}
\]
Similarly, we can directly derive \(Q(0,y)^{-1}\) without computing the multiplicative inverse of a formal power series. Then the algorithm for compositional inverse is:
```cpp#include<algorithm>#include<cassert>#include<cstdio>#include<tuple>#include<utility>#include<vector>usinguint=unsigned;usingull=unsignedlonglong;constexpruintMOD=998244353;constexpruintPowMod(uinta,ulle){for(uintres=1;;a=(ull)a*a%MOD){if(e&1)res=(ull)res*a%MOD;if((e/=2)==0)returnres;}}constexpruintInvMod(uinta){returnPowMod(a,MOD-2);}constexpruintQUAD_NONRESIDUE=3;constexprintLOG2_ORD=23;// __builtin_ctz(MOD - 1)constexpruintZETA=PowMod(QUAD_NONRESIDUE,(MOD-1)>>LOG2_ORD);constexpruintINV_ZETA=InvMod(ZETA);// Returns the array of roots of unity needed for an n-length FFT, length is half of nstd::pair<std::vector<uint>,std::vector<uint>>GetFFTRoot(intn){assert((n&(n-1))==0);if(n/2==0)return{};std::vector<uint>root(n/2),inv_root(n/2);root[0]=inv_root[0]=1;for(inti=0;(1<<i)<n/2;++i)root[1<<i]=PowMod(ZETA,1LL<<(LOG2_ORD-i-2)),inv_root[1<<i]=PowMod(INV_ZETA,1LL<<(LOG2_ORD-i-2));for(inti=1;i<n/2;++i)root[i]=(ull)root[i-(i&(i-1))]*root[i&(i-1)]%MOD,inv_root[i]=(ull)inv_root[i-(i&(i-1))]*inv_root[i&(i-1)]%MOD;return{root,inv_root};}voidButterfly(intn,uinta[],constuintroot[]){assert((n&(n-1))==0);for(inti=n;i>=2;i/=2)for(intj=0;j<n;j+=i)for(intk=j;k<j+i/2;++k){constuintu=a[k];a[k+i/2]=(ull)a[k+i/2]*root[j/i]%MOD;if((a[k]+=a[k+i/2])>=MOD)a[k]-=MOD;if((a[k+i/2]=u+MOD-a[k+i/2])>=MOD)a[k+i/2]-=MOD;}}voidInvButterfly(intn,uinta[],constuintroot[]){assert((n&(n-1))==0);for(inti=2;i<=n;i*=2)for(intj=0;j<n;j+=i)for(intk=j;k<j+i/2;++k){constuintu=a[k];if((a[k]+=a[k+i/2])>=MOD)a[k]-=MOD;a[k+i/2]=(ull)(u+MOD-a[k+i/2])*root[j/i]%MOD;}}voidFFT(intn,uinta[],constuintroot[]){Butterfly(n,a,root);}voidInvFFT(intn,uinta[],constuintroot[]){InvButterfly(n,a,root);constuintinv_n=InvMod(n);for(inti=0;i<n;++i)a[i]=(ull)a[i]*inv_n%MOD;}// Formal power series composition, compute f(g) mod x^n, requires g(0) = 0std::vector<uint>FPSComposition(std::vector<uint>f,std::vector<uint>g,intn){assert(g.empty()||g[0]==0);intlen=1;while(len<n)len*=2;std::vector<uint>root,inv_root;std::tie(root,inv_root)=GetFFTRoot(len*4);// [y^(-1)] (f(y) / (-g(x) + y)) mod x^n in R[x]((y^(-1)))constautoKinoshitaLi=[&](auto&&KinoshitaLi,conststd::vector<uint>&P,conststd::vector<uint>&Q,intd,intn){assert((int)P.size()==d*n);assert((int)Q.size()==d*n);if(n==1)returnP;std::vector<uint>dftQ(d*n*4);for(inti=0;i<d;++i)for(intj=0;j<n;++j)dftQ[i*n*2+j]=Q[i*n+j];dftQ[d*n*2]=1;FFT(d*n*4,dftQ.data(),root.data());std::vector<uint>V(d*n*2);for(inti=0;i<d*n*4;i+=2)V[i/2]=(ull)dftQ[i]*dftQ[i+1]%MOD;InvFFT(d*n*2,V.data(),inv_root.data());assert(V[0]==1);V[0]=0;for(inti=1;i<d*2;++i)for(intj=0;j<n/2;++j)V[i*(n/2)+j]=V[i*n+j];V.resize(d*n);conststd::vector<uint>T=KinoshitaLi(KinoshitaLi,P,V,d*2,n/2);std::vector<uint>dftT(d*n*2);for(inti=0;i<d*2;++i)for(intj=0;j<n/2;++j)dftT[i*n+j]=T[i*(n/2)+j];FFT(d*n*2,dftT.data(),root.data());for(inti=0;i<d*n*4;i+=2){constuintu=dftQ[i];dftQ[i]=(ull)dftT[i/2]*dftQ[i+1]%MOD;dftQ[i+1]=(ull)dftT[i/2]*u%MOD;}InvFFT(d*n*4,dftQ.data(),inv_root.data());for(inti=0;i<d;++i)for(intj=0;j<n;++j)dftQ[i*n+j]=dftQ[(i+d)*(n*2)+j];dftQ.resize(d*n);returndftQ;};f.resize(len);g.resize(len);for(inti=0;i<len;++i)g[i]=(g[i]!=0?MOD-g[i]:0);std::vector<uint>res=KinoshitaLi(KinoshitaLi,f,g,1,len);res.resize(n);returnres;}// Power Projection: [x^(n-1)] (fg^i) for i=0,..,n-1, requires g(0) = 0std::vector<uint>PowerProjection(std::vector<uint>f,std::vector<uint>g,intn){assert(g.empty()||g[0]==0);intlen=1;while(len<n)len*=2;std::vector<uint>root,inv_root;std::tie(root,inv_root)=GetFFTRoot(len*4);// [x^(n-1)] (f(x) / (-g(x) + y)) in R[x]((y^(-1)))constautoKinoshitaLi=[&](auto&&KinoshitaLi,std::vector<uint>P,std::vector<uint>Q,intd,intn)->std::vector<uint>{assert((int)P.size()==d*n);assert((int)Q.size()==d*n);if(n==1)returnP;std::vector<uint>dftQ(d*n*4),dftP(d*n*4);for(inti=0;i<d;++i)for(intj=0;j<n;++j)dftP[(2*(i+d)+1)*n+j]=P[i*n+j],dftQ[i*n*2+j]=Q[i*n+j];dftQ[d*n*2]=1;FFT(d*n*4,dftP.data(),inv_root.data());FFT(d*n*4,dftQ.data(),root.data());P.resize(d*n*2);Q.resize(d*n*2);for(inti=0;i<d*n*4;i+=2){P[i/2]=((ull)dftP[i]*dftQ[i+1]+(ull)dftP[i+1]*dftQ[i])%MOD;if(P[i/2]&1)P[i/2]+=MOD;P[i/2]/=2;Q[i/2]=(ull)dftQ[i]*dftQ[i+1]%MOD;}InvFFT(d*n*2,P.data(),root.data());InvFFT(d*n*2,Q.data(),inv_root.data());for(inti=0;i<d*2;++i)for(intj=0;j<n/2;++j)P[i*(n/2)+j]=P[i*n+n/2+j];assert(Q[0]==1);Q[0]=0;for(inti=1;i<d*2;++i)for(intj=0;j<n/2;++j)Q[i*(n/2)+j]=Q[i*n+j];P.resize(d*n);Q.resize(d*n);returnKinoshitaLi(KinoshitaLi,P,Q,d*2,n/2);};f.insert(f.begin(),len-n,0);f.resize(len);g.resize(len);std::reverse(f.begin(),f.end());for(inti=0;i<len;++i)g[i]=(g[i]!=0?MOD-g[i]:0);autores=KinoshitaLi(KinoshitaLi,f,g,1,len);res.resize(n);returnres;}// Formal power series power function, compute g^e mod x^n, requires g(0) = 1std::vector<uint>FPSPow1(std::vector<uint>g,uinte,intn){assert(!g.empty()&&g[0]==1);if(n==1)returnstd::vector<uint>{1u};std::vector<uint>inv(n),f(n);inv[1]=f[0]=1;for(inti=2;i<n;++i)inv[i]=(ull)(MOD-MOD/i)*inv[MOD%i]%MOD;for(inti=1;i<n;++i)f[i]=(ull)f[i-1]*(e+MOD+1-i)%MOD*inv[i]%MOD;g[0]=0;returnFPSComposition(f,g,n);}// Formal power series reversion (compositional inverse)// Compute g mod x^n such that g(f) = f(g) = x, requires g(0) = 0 and g'(0) ≠ 0std::vector<uint>FPSReversion(std::vector<uint>f,intn){assert(f.size()>=2&&f[0]==0&&f[1]!=0);if(n==1)returnstd::vector<uint>{0u};f.resize(n);constuintinvf1=InvMod(f[1]);uintinvf1p=1;for(inti=0;i<n;++i)f[i]=(ull)f[i]*invf1p%MOD,invf1p=(ull)invf1p*invf1%MOD;std::vector<uint>inv(n);inv[1]=1;for(inti=2;i<n;++i)inv[i]=(ull)(MOD-MOD/i)*inv[MOD%i]%MOD;std::vector<uint>proj=PowerProjection(std::vector<uint>{1u},f,n);for(inti=1;i<n;++i)proj[i]=(ull)proj[i]*(n-1)%MOD*inv[i]%MOD;std::reverse(proj.begin(),proj.end());std::vector<uint>res=FPSPow1(proj,InvMod(MOD+1-n),n-1);for(inti=0;i<n-1;++i)res[i]=(ull)res[i]*invf1%MOD;res.insert(res.begin(),0);returnres;}intmain(){intn;std::scanf("%d",&n);std::vector<uint>f(n);for(inti=0;i<n;++i)std::scanf("%u",&f[i]);conststd::vector<uint>res=FPSReversion(f,n);for(inti=0;i<n;++i)std::printf("%u%c",res[i]," \n"[i+1==n]);return0;}
```
Derived via Transposition Principle
The Power Projection problem is the transpose of the Modular Composition problem. Kinoshita and Li pointed out that our previous composition algorithm can be directly obtained by transposing the Power Projection algorithm. Similarly, any optimization applicable to the Power Projection algorithm can also be applied to the Modular Composition algorithm. We omit the details.
Daniel J. Bernstein. "Fast multiplication and its applications." Pages 325–384 in Algorithmic number theory: lattices, number fields, curves and cryptography, edited by Joe Buhler, Peter Stevenhagen, Cambridge University Press, 2008, ISBN 978-0521808545.