Hợp thành chuỗi lũy thừa hình thức | Nghịch đảo hợp thành
Chuỗi lũy thừa hình thức có phép hợp và nghịch hợp là các thao tác thường gặp. Với \(f\) không có tính chất đặc biệt, trước đây ta thường dùng thuật toán \(O\left(n^2\right)\) để tính \(f(g) \bmod{x^n}\) với \(f\in\mathbb{C}\left\lbrack\left\lbrack x\right\rbrack\right\rbrack,g\in x\mathbb{C}\left\lbrack\left\lbrack x\right\rbrack\right\rbrack\), nhưng do hiệu năng thấp nên ít ứng dụng. Ở đây giới thiệu thuật toán \(O\left(\mathsf{M}\left(n\right)\log n\right)\) của Kinoshita–Li, trong đó \(O\left(\mathsf{M}\left(n\right)\right)\) là thời gian nhân hai đa thức bậc \(O\left( n\right)\).
Hợp chuỗi lũy thừa hình thức/đa thức
Để tính \(f\left(g\left(x\right)\right)\bmod{x^n}\) thì cần mọi hệ số của \(f\left(g\left(x\right)\right)\) đều là tổng hữu hạn, nên trước đây yêu cầu \(f(x)\in\mathbb{C}\left\lbrack\left\lbrack x\right\rbrack\right\rbrack,g(x)\in x\mathbb{C}\left\lbrack\left\lbrack x\right\rbrack\right\rbrack\); nếu \(f(x),g(x)\in\mathbb{C}\left\lbrack x\right\rbrack\) thì cũng thỏa điều kiện này. Vì cần cắt cụt hệ số của \(f\left(g\left(x\right)\right)\), ta có thể trực tiếp xét trường hợp \(f(x),g(x)\) đều là đa thức. Với \(f(x)=\sum_{j=0}^{n-1}f_jx^j\), có
Khi tính \(\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)\), ta không cần giữ toàn bộ hệ số theo \(y\), vì cuối cùng chỉ cần hệ số của \(y^0\), nên các hệ số \(y^{>0}\) không cần. Mặt khác vì sau đó phải nhân với các đa thức dạng \(Q(-x,y)\in\mathbb{C}\left\lbrack x,y\right\rbrack\), nên chỉ cần giữ các hệ số có đóng góp đến \(y^0\). Ta đưa ra giả mã:
Lưu ý tham số thứ ba là vì \(g(0)\) có thể khác \(0\); nếu \(\deg f\geq n\) thì không thể cắt cụt \(f(x)\) để tính \(f\left(g(x)\right)\). Ta cũng có thể tính \(f(g)=f\circ \left(x+g(0)\right)\circ \left(g-g(0)\right)\), khi đó lấy \(F:=f\left(x+g(0)\right)\bmod{x^n}\) và \(G:=g-g(0)\) rồi tính \(\operatorname{\mathsf{Comp}}\left(F\left(y^{-1}\right),1-y\cdot G(x),n,1\right)\).
Ngoài ra, do giới hạn khi gọi đệ quy, ở điểm kết thúc ta có thể trực tiếp suy ra \(Q(0,y)^{-1}\), không cần dùng thuật toán nghịch đảo nhân của chuỗi lũy thừa; chỉ cần nhân một lần rồi trích hệ số cần thiết.
Trong tính nghịch hợp, ta cũng sẽ dùng phép tính lũy thừa.
Thay thế Kronecker
Trước khi phân tích độ phức tạp, ta xét cách nhân đa thức hai biến. Một ý tưởng là “đóng gói” hệ số; phương pháp này do Kronecker (1882) đề xuất bằng phép thế \(y\mapsto x^N\) để quy phép nhân trên \(R\left\lbrack x,y\right\rbrack\) về phép nhân trên \(R\left\lbrack x\right\rbrack\), nhưng cần \(N\) đủ lớn.
Giả sử \(\deg_x \left(AB\right)<N\), thì sau khi tính \(A\left(x,x^N\right)B\left(x,x^N\right)\) vẫn có thể khôi phục \(A(x,y)B(x,y)\), và thời gian “đóng gói/giải đóng gói” là tuyến tính.
Dùng thay thế Kronecker rồi nhân đa thức một biến, không khó thấy khi \(n\) là lũy thừa của \(2\), thuật toán trên chạy trong \(O\left(\mathsf{M}\left(n\right)\log n\right)\), vì mỗi lần đệ quy bậc theo \(y\) tăng gấp đôi, còn bậc theo \(x\) giảm một nửa.
#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);// 返回做 n 长 FFT 所需的单位根数组,长度为一半std::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;}// 形式幂级数复合,求出 f(g) mod x^n 要求 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;}// 多项式复合,求出 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;}
Nghịch hợp chuỗi lũy thừa hình thức
Cho \(f\in x\mathbb{C}\left\lbrack\left\lbrack x\right\rbrack\right\rbrack\) và \(f'(0)\neq 0\), cần tìm \(g(x)\bmod{x^n}\) sao cho \(f(g)\equiv g(f)\equiv x\pmod{x^n}\).
#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);// 返回做 n 长 FFT 所需的单位根数组,长度为一半std::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;}// 形式幂级数复合,求出 f(g) mod x^n 要求 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 要求 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;}// 形式幂级数幂函数,计算 g^e mod x^n 要求 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);}// 形式幂级数复合逆// 计算 g mod x^n 满足 g(f) = f(g) = x 要求 g(0) = 0 且 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;}
Suy ra từ nguyên lý chuyển vị
Bài toán Power Projection là chuyển vị của Modular Composition. Kinoshita và Li chỉ ra thuật toán hợp ở trên có thể suy ra trực tiếp từ Power Projection bằng phép chuyển vị. Tương tự, nếu có tối ưu cho Power Projection thì cũng áp dụng cho Modular Composition. Ta bỏ qua chi tiết.
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.
Last updated on this page:, Update history Found an error? Want to help improve? Edit this page on GitHub! Contributors to this page:OI-wiki All content on this page is provided under the terms of the CC BY-SA 4.0 and SATA license, additional terms may apply