小学乘法知识(可能连载)
FFT
板子
点此查看代码
#include <bits/stdc++.h> using namespace std; const int maxn=2097160; const int G=3,md=998244353; int n,m,i,j,k,u,v,p,t,le; int f[maxn],g[maxn],r[maxn]; inline int Pow(int d,int z){ int r=1; do{ if(z&1) r=1LL*r*d%md; d=1LL*d*d%md; }while(z>>=1); return r; } inline void Add(int &x,int y){x-=((x+=y)>=md)*md;} void dft(int *f){ for(i=1;i<t;++i) if(i<r[i]) swap(f[i],f[r[i]]); for(i=1,le=2;le<=t;i=le,le<<=1){ for(j=0,v=Pow(G,(md-1)/le);j<t;j+=le){ for(k=0,u=1;k<i;++k,u=1LL*u*v%md){ p=1LL*f[i+j+k]*u%md; Add(f[i+j+k]=f[j+k],md-p); Add(f[j+k],p); } } } } int main(){ scanf("%d%d",&n,&m); for(i=0;i<=n;++i) scanf("%d",f+i); for(i=0;i<=m;++i) scanf("%d",g+i); for(n+=m,t=1;t<=n;t<<=1); for(i=1;i<t;++i) r[i]=(r[i>>1]>>1)|((i&1)*(t>>1)); dft(f); dft(g); for(i=0;i<t;++i) f[i]=1LL*f[i]*g[i]%md; dft(f); reverse(f+1,f+t); p=Pow(t,md-2); for(i=0;i<=n;++i) printf("%d ",1LL*f[i]*p%md); return 0; }
属于需要背的知识。原根表。常见的原根形如 原根为 , 原根为 。
分治 FFT
例题:P4721 【模板】分治 FFT
令 。考虑在知道 时,其对 会造成怎样的贡献。此时 ,此时将 和 卷积可得对应的贡献,分治下去计算其他部分的贡献即可。
使用多项式求逆:考虑 的生成函数 ,则由 有
由于 没有对 产生贡献,所以可以令 ,则 。可以多项式求逆解决。
代码
点此查看代码
#include <bits/stdc++.h> using namespace std; const int maxn=100010; const int maxb=262150; const int md=998244353,_g=3; int n,i,j,k,u,v,t,p,le,f[maxn],g[maxn]; int r[maxb],F[maxb],G[maxb]; inline int Pow(int d,int z){ int r=1; do{ if(z&1) r=1LL*r*d%md; d=1LL*d*d%md; }while(z>>=1); return r; } inline void Add(int &x,int y){x-=((x+=y)>=md)*md;} void dft(int *f){ for(i=1;i<t;++i) if(i<r[i]) swap(f[i],f[r[i]]); for(i=1,le=2;le<=t;i=le,le<<=1){ v=Pow(_g,(md-1)/le); for(j=0;j<t;j+=le){ for(k=0,u=1;k<i;++k,u=1LL*u*v%md){ p=1LL*f[i+j+k]*u%md; Add(f[i+j+k]=f[j+k],md-p); Add(f[j+k],p); } } } } void Solve(int l,int r){ if(l==r) return; int m=(l+r)>>1; Solve(l,m); for(t=1,k=r-l+m-l-1;t<=k;t<<=1); memset(F,0,t<<2); memset(G,0,t<<2); memcpy(F,f+l,(m-l+1)<<2); memcpy(G,g+1,(r-l)<<2); for(i=1;i<t;++i) ::r[i]=(::r[i>>1]>>1)|((i&1)*(t>>1)); dft(F); dft(G); for(i=0;i<t;++i) F[i]=(1LL*F[i]*G[i])%md; dft(F); reverse(F+1,F+t); p=Pow(t,md-2); for(i=m+1;i<=r;++i) Add(f[i],1LL*F[i-l-1]*p%md); Solve(m+1,r); } int main(){ scanf("%d",&n); --n; f[0]=1; for(i=1;i<=n;++i) scanf("%d",g+i); Solve(0,n); for(i=0;i<=n;++i) printf("%d ",f[i]); return 0; }
多项式牛顿迭代
引入:导数
导数表
(其中 表示和 无关的常数,下同)
导数/积分运算法则
乘法法则:
除法法则:
链式法则:
复合函数求导:
分部积分:
洛必达法则:如果 ,且 在 的某去心邻域内两者均可导且 ,则有 。如果 ,且 在 的某去心邻域内两者均可导且 ,则有 。
引入:泰勒展开
在逼*某个函数 时,可以考虑使用某个多项式,通过对 在某个位置 的 阶导数求出在同样位置处 阶导数相同的多项式,从而逼* ,由 有 。某些时候可以直接取 ,则有 。一些例子。
引入:牛顿迭代
在求解方程 的解时,我们考虑使用靠*零点的切线逼*解。具体地,取函数的某个位置 ,得出在 位置和 相切的切线 。此时取该切线的零点 ,然后再求出 位置和 相切的切线 ;就可以不断迭代下去以逼*该解(显然无法得到解的精确值)。
虽然某些时候不能用牛顿迭代法逼*解,但是这个算法有较为广泛的应用,例如 求某个数的*似*方根。同时牛顿迭代法收敛速度(逼*正解速度)快,可以说每次迭代后有效数位均会上升一倍。
在已知多项式 ,求满足 的 时,同样可以考虑牛顿迭代法的思路。设已经求出了 部分的解 ,(注意需要单独求出常数项)则求 时可以考虑将 在 处泰勒展开得:
然后考虑 的最高次数为 ,所以 的最低次数非零项的次数为 ,则上式 等效于 ,解得 。
多项式牛顿迭代可以用于求多项式经过某些运算后的值。例如求多项式 的逆时可以构造 ,开方时可以构造 ,求 时可以构造 。
多项式求逆
构造 ,在求出上述的 后,由于 ,则有 。
代码
点此查看代码
#include <bits/stdc++.h> using namespace std; const int maxb=262150; const int md=998244353,G=3; int n,i,j,k,u,v,p,d,t,le; int f[maxb],g[maxb],h[maxb],r[maxb],x[maxb]; inline int Pow(int d,int z){ int r=1; do{ if(z&1) r=1LL*r*d%md; d=1LL*d*d%md; }while(z>>=1); return r; } inline void Add(int &x,int y){x-=((x+=y)>=md)*md;} void dft(int *f){ for(i=1;i<t;++i) if(i<r[i]) swap(f[i],f[r[i]]); for(i=1,le=2;le<=t;i=le,le<<=1){ for(j=0,v=Pow(G,(md-1)/le);j<t;j+=le){ for(k=0,u=1;k<i;++k,u=1LL*u*v%md){ p=1LL*f[i+j+k]*u%md; Add(f[i+j+k]=f[j+k],md-p); Add(f[j+k],p); } } } } int main(){ scanf("%d",&n); for(i=0;i<n;++i) scanf("%d",h+i); n<<=1; f[0]=Pow(h[0],md-2); Add(x[0]=f[0],f[0]); for(d=2,t=4;d<=n;d=t,t<<=1){ for(i=0;i<t;++i) r[i]=(r[i>>1]>>1)+(i&1)*d; memcpy(g,h,d<<2); dft(g); dft(f); for(i=0;i<t;++i) f[i]=1LL*f[i]*f[i]%md*g[i]%md; dft(f); reverse(f+1,f+t); p=Pow(t,md-2); for(i=0;i<d;++i){ Add(f[i]=md-1LL*f[i]*p%md,x[i]); Add(x[i]=f[i],f[i]); f[i+d]=0; } } for(n>>=1,i=0;i<n;++i) printf("%d ",f[i]); return 0; }
多项式
考虑 。此时我们需要求 ,两边同时求导有 。注意此时 ,而 为超越数(不能作为某个方程 的根,其中 均为整数,所以不能在模意义下被表示),所以 在 不为 时不能在模意义下被表示。
代码
点此查看代码
#include <bits/stdc++.h> using namespace std; const int maxb=262150; const int md=998244353,G=3; int n,i,j,k,u,v,d,t,p,le; int f[maxb],g[maxb],h[maxb],x[maxb],r[maxb]; inline int Pow(int d,int z){ int r=1; do{ if(z&1) r=1LL*r*d%md; d=1LL*d*d%md; }while(z>>=1); return r; } inline void Add(int &x,int y){x-=((x+=y)>=md)*md;} void dft(int *f){ for(i=1;i<t;++i) if(i<r[i]) swap(f[i],f[r[i]]); for(i=1,le=2;le<=t;i=le,le<<=1){ for(j=0,v=Pow(G,(md-1)/le);j<t;j+=le){ for(k=0,u=1;k<i;++k,u=1LL*u*v%md){ p=1LL*f[i+j+k]*u%md; Add(f[i+j+k]=f[j+k],md-p); Add(f[j+k],p); } } } } int main(){ scanf("%d",&n); for(i=0;i<n;++i) scanf("%d",h+i); n<<=1; for(f[0]=1,d=x[0]=2,t=4;d<=n;d=t,t<<=1){ for(i=0;i<t;++i) r[i]=(r[i>>1]>>1)+(i&1)*d; memcpy(g,h,d<<2); dft(f); dft(g); for(i=0;i<t;++i) f[i]=1LL*f[i]*f[i]%md*g[i]%md; dft(f); reverse(f+1,f+t); p=Pow(t,md-2); for(i=0;i<d;++i){ Add(f[i]=md-1LL*f[i]*p%md,x[i]); Add(x[i]=f[i],f[i]); f[i+d]=0; } } for(n>>=1,i=1;i<n;++i) h[i-1]=1LL*h[i]*i%md; h[n]=0; memset(f+n,0,(d-n)<<2); for(t=1;t<=(n-1)<<1;t<<=1); for(i=0;i<t;++i) r[i]=(r[i>>1]>>1)+(i&1)*(t>>1); dft(f); dft(h); for(i=0;i<t;++i) f[i]=1LL*f[i]*h[i]%md; dft(f); reverse(f+1,f+t); p=Pow(t,md-2); for(i=0;i<t;++i) f[i]=1LL*f[i]*p%md; for(i=n-1;i;--i) f[i]=1LL*f[i-1]*Pow(i,md-2)%md; for(f[0]=i=0;i<n;++i) printf("%d ",f[i]); return 0; }
多项式
构造 ,在求出上述的 后,由于 ,则有 。
代码
懒得封装,写得比较丑。
点此查看代码
#include <bits/stdc++.h> using namespace std; const int maxb=262150; const int maxt=524300; const int md=998244353,G=3; int n,i,j,k,u,v,p,t,le,d0,t0,d1; int f0[maxb],f1[maxt],g1[maxb],g[maxt],h[maxb],x[maxt],r[maxt]; inline int Pow(int d,int z){ int r=1; do{ if(z&1) r=1LL*r*d%md; d=1LL*d*d%md; }while(z>>=1); return r; } inline void Add(int &x,int y){x-=((x+=y)>=md)*md;} void dft(int *f){ for(i=1;i<t;++i) if(i<r[i]) swap(f[i],f[r[i]]); for(i=1,le=2;le<=t;i=le,le<<=1){ for(j=0,v=Pow(G,(md-1)/le);j<t;j+=le){ for(k=0,u=1;k<i;++k,u=1LL*u*v%md){ p=1LL*f[i+j+k]*u%md; Add(f[i+j+k]=f[j+k],md-p); Add(f[j+k],p); } } } } int main(){ scanf("%d",&n); for(i=1;i<n;++i) scanf("%d",h+i); n<<=1; for(f0[0]=1,d0=2,t0=4;d0<=n;d0=t0,t0<<=1){ //外层倍增求 exp for(i=1;i<d0;++i) g1[i-1]=1LL*f0[i]*i%md; //求导 for(f1[0]=1,x[0]=d1=2,t=4;d1<=t0;d1=t,t<<=1){ //内层倍增求逆 for(i=1;i<t;++i) r[i]=(r[i>>1]>>1)|((i&1)*d1); memcpy(g,f0,d1<<2); dft(f1); dft(g); for(i=0;i<t;++i) f1[i]=1LL*f1[i]*f1[i]%md*g[i]%md; dft(f1); reverse(f1+1,f1+t); p=Pow(t,md-2); for(i=0;i<d1;++i){ Add(f1[i]=md-1LL*f1[i]*p%md,x[i]); Add(x[i]=f1[i],f1[i]); f1[i+d1]=0; } } memset(f1+d0,0,(d1-d0)<<2); for(t=1;t<=t0-2;t<<=1); for(i=1;i<t;++i) r[i]=(r[i>>1]>>1)|((i&1)*(t>>1)); dft(f1); dft(g1); for(i=0;i<t;++i) f1[i]=1LL*f1[i]*g1[i]%md; dft(f1); reverse(f1+1,f1+t); p=Pow(t,md-2); memset(f1+d0,0,(t-d0)<<2); memset(g1,0,t<<2); for(i=0;i<d0;++i) f1[i]=1LL*f1[i]*p%md; //f'(x)/f(x) for(i=d0-1;i;--i) f1[i]=1LL*f1[i-1]*Pow(i,md-2)%md; //积分 for(f1[0]=i=1;i<d0;++i) f1[i]=(1LL*f1[i]*(md-1)+h[i])%md; for(i=1,t=t0;i<t;++i) r[i]=(r[i>>1]>>1)|((i&1)*d0); dft(f0); dft(f1); for(i=0;i<t;++i) f0[i]=1LL*f0[i]*f1[i]%md; dft(f0); reverse(f0+1,f0+t); p=Pow(t,md-2); for(i=0;i<d0;++i) f0[i]=1LL*f0[i]*p%md; //求 exp memset(f0+d0,0,d0<<2); memset(f1,0,t<<2); memset(g,0,d1<<2); } for(n>>=1,i=0;i<n;++i) printf("%d ",f0[i]); return 0; }
附: 求逆/ / 的做法
可以在多项式次数小的时候进行优化( 的一种卡常就是分治 FFT 处理下面的 形式)时间/码长。
求逆:考虑 时由于 ,所以 。
:令 。考虑 时(此时有 ),有:
多项式带余除法(待补)
多项式多点求值(待补)
拉格朗日插值
在已知某个不超过 次的多项式上的 个点时,可以使用拉格朗日插值法确定出这个唯一的多项式。设第 个点为 。
对于多项式 ,由 有 。此时可以列出方程组如下:
考虑使用中国剩余定理(可以将所有 看成两两互质),令 , 为模 意义下 的逆元,由于 ,则 ;答案为 (其对应次数小于 ,所以在模 意义下解就是这个式子)。
代码
点此查看代码
#include <bits/stdc++.h> using namespace std; const int maxn=2010; const int md=998244353; int n,k,i,j,a,b,c,s,x[maxn],y[maxn]; inline int Pow(int d,int z){ int r=1; do{ if(z&1) r=1LL*r*d%md; d=1LL*d*d%md; }while(z>>=1); return r; } int main(){ scanf("%d%d",&n,&k); for(i=1;i<=n;++i) scanf("%d%d",x+i,y+i); for(i=1;i<=n;++i,s=(1LL*a*b%md*Pow(c,md-2)+s)%md) for(a=y[i],j=b=c=1;j<=n;++j) if(j!=i) b=1LL*b*(k+md-x[j])%md,c=1LL*c*(x[i]+md-x[j])%md; printf("%d",s); return 0; }
FMT/FWT(个人的理解)
此处用了一种较为清奇(?)的思路。
(1) 或卷积(求 )
考虑多项式乘法的过程:我们会先对序列进行 DFT,然后相乘,最后进行 IDFT。此时可以使用类似的方式,用某种序列表示需要操作的序列。
令 ,则 。而 等效于 是 的子集,所以 当且仅当 均是 的子集,也就是说 。求 可以使用上述的方法,而从 变回 时,考虑上述 dp 过程的逆过程:在从 推到 时,如果某个 的第 位为 则 ,否则 ,直接 dp 即可。
(2) 与卷积(求 )
仍然可以考虑上述的过程,设 ,则同样有 当且仅当 ,所以仍然有 。求 时仍然可以使用上面 dp 的思路,但是转移时有 。从 推回 同理。
(3) 异或卷积(求 )
考虑异或的性质,令 为 在二进制表示下的位数,可以发现 ,同理有 。令 ,则 。 也可以使用上述的 dp 方式计算。不过有更简洁的方式计算:设 ,则 。同或卷积可以直接将求得的 翻转即可。
(4) 子集卷积(求 )
考虑第二个性质等效于 。可以把所有下标按照 的不同值分开处理,设 为 ,则 (暴力计算即可,节省常数)。这里的 被称为 的 占位幂级数。
集合幂级数进阶计算
考虑将上述的子集卷积看成乘法,并以此为基础定义求逆 / / 等运算。此时可以将子集卷积中对占位多项式的卷积操作改成多项式求逆 / / ,其他操作不变即可。此时集合幂级数的 和多项式的对应 的组合意义有相似之处;例如多项式 对应了将全集划分为有标号元素组成的无标号集合的方案,则集合幂级数 对应了将全集划分成若干子集(满足 子集不同 对应的方案就会不同)的对应方案。可以使用上述的 求逆 / / 的做法节省常数。
本文来自博客园,作者:Fran-Cen,转载请注明原文链接:https://www.cnblogs.com/Fran-CENSORED-Cwoi/p/17137654.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!