前言
文章目录
FFT
定义啊证明啊先咕咕咕了
放个版溜了
typedef complex<double> cpd;
void FFT(cpd p[],int n,int dir){
for(int i=1,j=0;i<n-1;i++){
for(int s=n;j^=s>>=1,~j&s;) ;
if(i<j){
swap(p[i],p[j]);
}
}
for(int d=0;(1<<d)<n;d++){
int m=1<<d,m2=m*2;
double p0=pi/m*dir;
cpd wn(cos(p0),sin(p0));
for(int i=0;i<n;i+=m2){
cpd w(1,0);
for(int j=0;j<m;j++){
cpd t0=p[i+j],t1=p[i+j+m];
p[i+j]=t0+t1*w;
p[i+j+m]=t0-t1*w;
w=w*wn;
}
}
}
if(dir==-1)
for(int i=0;i<n;i++)
p[i].real()/=1.0*n;
}
这个版注意一下,图省事直接用的复数类,所以效率比较慢,而且real()根据编译器问题可能CE?
NTT
其实啊…跟FFT差不了多少
void NTT(int *p,int n,int dir){
for(int i=1,j=0;i<n-1;i++){
for(int s=n;j^=s>>=1,~j&s;);
if(i<j)
swap(p[i],p[j]);
}
for(int m=1;m<n;m<<=1){
int m2=m*2;
int wn=ksm(g,(mod-1)/m2);
if(dir==-1)
wn=ksm(wn,mod-2);
for(int i=0;i<n;i+=m2){
int w=1;
for(int j=0;j<m;j++){
int t0=p[i+j],t1=p[i+j+m];
p[i+j]=(t0+1ll*t1*w%mod)%mod;
p[i+j+m]=(t0-1ll*t1*w%mod+mod)%mod;
w=1ll*w*wn%mod;
}
}
}
if(dir==-1){
int inv=ksm(n,mod-2);
for(int i=0;i<n;i++)
p[i]=1ll*p[i]*inv%mod;
}
}
多项式的乘法
em…就是卷积嘛…
上FFT或者NTT即可
注意,一定注意清零的问题,下面的算法也是一样
//注意执行完这个函数后a、b会改变哦
void Mul(Poly &a,Poly &b,int n,Poly &res){
int len=1;
while(len<2*n)
len<<=1;
NTT(a,len,1);
NTT(b,len,1);
for(int i=0;i<len;i++)
res[i]=1ll*a[i]*b[i]%mod;
NTT(res,len,-1);
}
多项式求逆元
当n=1时,就剩常数项了,直接逆元
当n>1时,假设我们已经求出了关于
的逆元,也就是说
用原式-①,有
两边平方,有
由于,左边那个东西在0到(n-1)/2上都是0,所以平方之后,0到n-1上一定都是0
两边同时乘上
,移项,有
右边那一坨每次用NTT去搞就好了
void Inv(const Poly &a,Poly &inv,int n){
if(n==1){
inv[0]=ksm(a[0],mod-2);
return ;
}
Inv(a,inv,(n+1)/2);
int len=1;
while(len<2*n)
len<<=1;
copy(a,a+n,t);
fill(t+n,t+len,0);
fill(inv+n,inv+len,0);
NTT(t,len,1);
NTT(inv,len,1);
for(int i=0;i<len;i++)
inv[i]=1ll*inv[i]*((2-1ll*t[i]*inv[i]%mod+mod)%mod)%mod;
NTT(inv,len,-1);
fill(inv+n,inv+len,0);
}
多项式取模
设
那么
观察可以发现
顺手取个模,
右边就逆元搞定咯
然后就可以求出
然后再翻转,就得到了
那么
搞定。
void Mod(Poly &a,const Poly &p,int n,int m){
copy(a,a+n,ra);
reverse(ra,ra+n);
copy(p,p+m,rp);
reverse(rp,rp+m);
Inv(rp,rpi,n-m+1);
int len=1;
while(len<(n-m+1)*2)
len<<=1;
fill(ra+n-m+1,ra+len,0);
fill(rpi+n-m+1,rpi+len,0);
Mul(ra,rpi,n-m+1,c);
len=1;
while(len<2*n)
len<<=1;
fill(c+n-m+1,c+len,0);
reverse(c,c+n-m+1);
copy(p,p+m,t);
fill(t+m,t+len,0);
Mul(c,t,n,c);
for(int i=0;i<n;i++)
a[i]=(a[i]-c[i]+mod)%mod;
}
多项式的多点求值
问题描述:给出一个多项式A(x),和n个横坐标xi,求出每个对应的A(xi)
我们令所有要求值的横坐标集合为X
然后定义
然后我们定义两个多项式
那么可以有
也即是说
同理有
然后往下带
然后到n=1的时候,就可以直接算出答案了
时间复杂度
以上。
模板题
#include<cstdio>
#include<cstring>
#include<algorithm>
#define lch x<<1
#define rch x<<1|1
using namespace std;
typedef long long ll;
const int N=64005*8;
const int mod=998244353;
const int g=3;
int n,m;
void Debug(const int *p,char c,int n=8){
putchar(c);
for(int i=0;i<n;i++)
printf("%d ",p[i]);
puts("");
}
int ksm(int x,int y){
int res=1;
while(y){
if(y&1)
res=1ll*res*x%mod;
x=1ll*x*x%mod;
y>>=1;
}
return res;
}
void NTT(int *p,int n,int dir){
for(int i=1,j=0;i<n-1;i++){
for(int s=n;j^=s>>=1,~j&s;);
if(i<j)
swap(p[i],p[j]);
}
for(int m=1;m<n;m<<=1){
int m2=m*2;
int wn=ksm(g,(mod-1)/m2);
if(dir==-1)
wn=ksm(wn,mod-2);
for(int i=0;i<n;i+=m2){
int w=1;
for(int j=0;j<m;j++){
int t0=p[i+j],t1=p[i+j+m];
p[i+j]=(t0+1ll*t1*w%mod)%mod;
p[i+j+m]=(t0-1ll*t1*w%mod+mod)%mod;
w=1ll*w*wn%mod;
}
}
}
if(dir==-1){
int inv=ksm(n,mod-2);
for(int i=0;i<n;i++)
p[i]=1ll*p[i]*inv%mod;
}
}
void Mul(int *a,int *b,int n,int *res){
int len=1;
while(len<2*n)
len<<=1;
fill(a+n,a+len,0);
fill(b+n,b+len,0);
NTT(a,len,1);
NTT(b,len,1);
for(int i=0;i<len;i++)
res[i]=1ll*a[i]*b[i]%mod;
NTT(res,len,-1);
//fill(res+n,res+len,0);
}
void Inv(const int *a,int *inv,int n){
static int t[N];
if(n==1){
inv[0]=ksm(a[0],mod-2);
return ;
}
Inv(a,inv,(n+1)/2);
int len=1;
while(len<2*n)
len<<=1;
copy(a,a+n,t);
fill(t+n,t+len,0);
fill(inv+n,inv+len,0);
NTT(t,len,1);
NTT(inv,len,1);
for(int i=0;i<len;i++)
inv[i]=1ll*inv[i]*((2-1ll*inv[i]*t[i]%mod+mod)%mod)%mod;
NTT(inv,len,-1);
fill(inv+n,inv+len,0);
}
void Mod(int *a,const int *p,int n,int m){
static int ra[N],rp[N],rpi[N],c[N],d[N];
copy(a,a+n,ra);
reverse(ra,ra+n);
copy(p,p+m,rp);
reverse(rp,rp+m);
Inv(rp,rpi,n-m+1);
int len=1;
while(len<(n-m+1)*2)
len<<=1;
fill(ra+n-m+1,ra+len,0);
fill(rpi+n-m+1,rpi+len,0);
Mul(ra,rpi,n-m+1,c);
len=1;
while(len<2*n)
len<<=1;
fill(c+n-m+1,c+len,0);
reverse(c,c+n-m+1);
copy(p,p+m,d);
fill(d+m,d+len,0);
Mul(c,d,n,c);
for(int i=0;i<n;i++)
a[i]=(a[i]-c[i]+mod)%mod;
}
int *P[N*4],len[N*4];
int a[N],b[N];
void Init_P(int x,int l,int r){
static int t1[N],t2[N];
if(l==r){
len[x]=1;
P[x]=new int[2];
P[x][0]=(mod-b[l]%mod);
P[x][1]=1;
return ;
}
int mid=(l+r)>>1;
Init_P(lch,l,mid);
Init_P(rch,mid+1,r);
len[x]=(len[lch]+len[rch]);
//Debug(P[lch],'l',len[lch]+1);
//Debug(P[rch],'r',len[lch]+1);
P[x]=new int [len[x]+1];
int n=len[x]+1,Len=1;
while(Len<n*2)
Len<<=1;
copy(P[lch],P[lch]+len[lch]+1,t1);
fill(t1+len[lch]+1,t1+Len,0);
copy(P[rch],P[rch]+len[rch]+1,t2);
fill(t2+len[rch]+1,t2+Len,0);
//Debug(t1,'@');
//Debug(t2,'@');
Mul(t1,t2,n,t1);
//Debug(t1,'!');
for(int i=0;i<n;i++)
P[x][i]=t1[i];
}
int ans[N];
void calc(int x,int l,int r,const int *A){
if(l==r){
ans[l]=A[0];
return ;
}
int mid=(l+r)>>1;
int B[(len[x]+2)<<2];
int n=len[x]+1;
int Len=1;
while(Len<n*2)
Len<<=1;
copy(A,A+n,B);
fill(B+n,B+Len,0);
//Debug(B,'$');
//Debug(P[lch],'$',len[lch]+1);
Mod(B,P[lch],n,len[lch]+1);
//Debug(B,'#');
calc(lch,l,mid,B);
copy(A,A+n,B);
fill(B+n,B+Len,0);
Mod(B,P[rch],n,len[rch]+1);
calc(rch,mid+1,r,B);
}
void Read(int &x){
x=0;
char c=getchar();
while(c<'0'||c>'9')
c=getchar();
while(c>='0'&&c<='9')
x=x*10+c-'0',c=getchar();
}
int main()
{
//freopen("1.in","r",stdin);
//scanf("%d%d",&n,&m);
Read(n),Read(m);
for(int i=0;i<=n;i++){
//scanf("%d",&a[i]);
Read(a[i]);
}
for(int i=1;i<=m;i++){
//scanf("%d",&b[i]);
Read(b[i]);
}
Init_P(1,1,m);
/*if(n>=m)
Mod(a,P[1],n+1,m+1);*/
calc(1,1,m,a);
for(int i=1;i<=m;i++)
printf("%d\n",ans[i]);
}
拉格朗日插值法
问题描述:给出一个n-1次多项式上的n个点,求
然而似乎不能求出来
的具体表达式,只能对于一个特定的
求出对应的
直接上公式了,证明直接咕掉
如果预处理逆元的话,就
咯
多项式的快速插值
法一
这个方法我没写哦,先咕了,就当是扩展思路?
还是按照上面类似的方法
假如我们已经求出了
所对应的插值多项式
设
那么再设
当
时,显然成立
那么当
时,有
也就是说
后面那坨可以看做是一个新的点
在
上,那么就也可以用快速插值解决
至于
的值,可以用多点求值解决。
总时间复杂度
还有
的算法呢
法二
首先,由拉格朗日插值法有
变形,有
我们令
那么,
记左式为H(x)
求导
令x=xi,有
那么,
然后呢,我们令
,
再令
那么,
可以预先NTT分治得到
可以用多点求值得到
然后就再做一次分治即可。
总时间复杂度
#include<cstdio>
#include<cstring>
#include<algorithm>
#define lch x<<1
#define rch x<<1|1
using namespace std;
typedef long long ll;
const int N=100005*4;
const int mod=998244353;
const int g=3;
int n,m;
void Debug(const int *p,char c,int n=8){
putchar(c);
for(int i=0;i<n;i++)
printf("%d ",p[i]);
puts("");
}
int ksm(int x,int y){
int res=1;
while(y){
if(y&1)
res=1ll*res*x%mod;
x=1ll*x*x%mod;
y>>=1;
}
return res;
}
int t_wn[N],t_wni[N];
void Prepare(){
for(int m=1;m<N;m<<=1){
int m2=m*2;
t_wn[m]=ksm(g,(mod-1)/m2);
t_wni[m]=ksm(t_wn[m],mod-2);
}
}
void NTT(int *p,int n,int dir){
for(int i=1,j=0;i<n-1;i++){
for(int s=n;j^=s>>=1,~j&s;);
if(i<j)
swap(p[i],p[j]);
}
for(int m=1;m<n;m<<=1){
int m2=m*2;
/*int wn=ksm(g,(mod-1)/m2);
if(dir==-1)
wn=ksm(wn,mod-2);*/
int wn;
if(dir==1){
wn=t_wn[m];
}
else{
wn=t_wni[m];
}
for(int i=0;i<n;i+=m2){
int w=1;
for(int j=0;j<m;j++){
int t0=p[i+j],t1=p[i+j+m];
p[i+j]=(t0+1ll*t1*w%mod)%mod;
p[i+j+m]=(t0-1ll*t1*w%mod+mod)%mod;
w=1ll*w*wn%mod;
}
}
}
if(dir==-1){
int inv=ksm(n,mod-2);
for(int i=0;i<n;i++)
p[i]=1ll*p[i]*inv%mod;
}
}
void Mul(int *a,int *b,int len,int *res){
NTT(a,len,1);
NTT(b,len,1);
for(int i=0;i<len;i++)
res[i]=1ll*a[i]*b[i]%mod;
NTT(res,len,-1);
//fill(res+n,res+len,0);
}
void Inv(const int *a,int *inv,int n){
static int t[N];
if(n==1){
inv[0]=ksm(a[0],mod-2);
return ;
}
Inv(a,inv,(n+1)/2);
int len=1;
while(len<n*2)
len<<=1;
copy(a,a+n,t);
fill(t+n,t+len,0);
fill(inv+n,inv+len,0);
NTT(t,len,1);
NTT(inv,len,1);
for(int i=0;i<len;i++)
inv[i]=1ll*inv[i]*((2-1ll*inv[i]*t[i]%mod+mod)%mod)%mod;
NTT(inv,len,-1);
fill(inv+n,inv+len,0);
}
void Mod(int *a,const int *p,int n,int m){
static int ra[N],rp[N],rpi[N],c[N],d[N];
copy(a,a+n,ra);
reverse(ra,ra+n);
copy(p,p+m,rp);
reverse(rp,rp+m);
Inv(rp,rpi,n-m+1);
int len=1;
while(len<(n-m+1)*2)
len<<=1;
fill(ra+n-m+1,ra+len,0);
fill(rpi+n-m+1,rpi+len,0);
Mul(ra,rpi,len,c);
len=1;
while(len<n)
len<<=1;
fill(c+n-m+1,c+len,0);
reverse(c,c+n-m+1);
copy(p,p+m,d);
fill(d+m,d+len,0);
Mul(c,d,len,c);
for(int i=0;i<n;i++)
a[i]=(a[i]-c[i]+mod)%mod;
}
int *P[N*4],len[N*4];
int z[N];
int X[N],Y[N];
void Init_P(int x,int l,int r){
static int t1[N],t2[N];
if(l==r){
len[x]=1;
P[x]=new int[2];
P[x][0]=(mod-X[l]%mod);
P[x][1]=1;
return ;
}
int mid=(l+r)>>1;
Init_P(lch,l,mid);
Init_P(rch,mid+1,r);
len[x]=(len[lch]+len[rch]);
//Debug(P[lch],'l',len[lch]+1);
//Debug(P[rch],'r',len[lch]+1);
P[x]=new int [len[x]+1];
int n=len[x]+1,Len=1;
while(Len<n)
Len<<=1;
int lsz=len[lch]+1,rsz=len[rch]+1;
copy(P[lch],P[lch]+lsz,t1);
fill(t1+lsz,t1+Len,0);
copy(P[rch],P[rch]+rsz,t2);
fill(t2+rsz,t2+Len,0);
//Debug(t1,'@');
//Debug(t2,'@');
Mul(t1,t2,Len,t1);
//Debug(t1,'!');
for(int i=0;i<n;i++)
P[x][i]=t1[i];
}
void calc(int x,int l,int r,const int *A){
if(l==r){
z[l]=(1ll*Y[l]*ksm(A[0],mod-2)%mod+mod)%mod;
return ;
}
int mid=(l+r)>>1;
int n=len[x]+1;
int Len=1;
while(Len<n)
Len<<=1;
int B[Len];
copy(A,A+n,B);
fill(B+n,B+Len,0);
//Debug(B,'$');
//Debug(P[lch],'$',len[lch]+1);
Mod(B,P[lch],n,len[lch]+1);
//Debug(B,'#');
calc(lch,l,mid,B);
copy(A,A+n,B);
fill(B+n,B+Len,0);
Mod(B,P[rch],n,len[rch]+1);
calc(rch,mid+1,r,B);
}
void qiudao(int *x,int n){
for(int i=0;i<n;i++){
if(i){
x[i-1]=1ll*i*x[i]%mod;
}
x[i]=0;
}
}
int G[N];
void Solve(int x,int l,int r,int *F){
static int p0[N],p1[N];
if(l==r){
F[0]=1ll*z[l]%mod;
return ;
}
int lsz=len[lch]+1;
int rsz=len[rch]+1;
int Len=1;
int n=len[x]+1;
while(Len<n)
Len<<=1;
int L[Len];
int R[Len];
fill(L,L+Len,0);
fill(R,R+Len,0);
int mid=(l+r)>>1;
Solve(lch,l,mid,L);
Solve(rch,mid+1,r,R);
//puts("!");
//Debug(L,'L',(len[lch]+1)<<1);
//Debug(R,'R',(len[rch]+1)<<1);
copy(P[lch],P[lch]+lsz,p0);
fill(p0+lsz,p0+Len,0);
copy(P[rch],P[rch]+rsz,p1);
fill(p1+rsz,p1+Len,0);
//Debug(p0,'(',Len);
//Debug(p1,')',Len);
fill(L+lsz,L+Len,0);
fill(R+rsz,R+Len,0);
Mul(p0,R,Len,p0);
Mul(p1,L,Len,p1);
//Debug(p0,'(',Len);
//Debug(p1,')',Len);
for(int i=0;i<n;i++)
F[i]=(p0[i]+p1[i])%mod;
}
int ans[N];
void Read(int &x){
x=0;
char c=getchar();
while(c<'0'||c>'9')
c=getchar();
while(c>='0'&&c<='9')
x=x*10+c-'0',c=getchar();
}
void Print(int x){
if(x>9)
Print(x/10);
putchar(x%10+'0');
}
int main()
{
//freopen("1.in","r",stdin);
//ReadOptmized();
Prepare();
Read(n);
for(int i=1;i<=n;i++){
Read(X[i]);
Read(Y[i]);
}
Init_P(1,1,n);
copy(P[1],P[1]+n+1,G);
//Debug(G,'!');
qiudao(G,n+1);
//Debug(G,'@');
calc(1,1,n,G);
Solve(1,1,n,ans);
//puts("?");
for(int i=0;i<n;i++){
//printf("%d ",ans[i]);
Print(ans[i]);
putchar(' ');
}
//return 0;
//printf("success\n");
}
多项式的开方
已知A(x),
求B(x)
假设有
两式相减再平方,有
此处模数平方的原因是跟求逆元时的解释一样的
那么最后就有
void Sqrt(const int *a,int *b,int n){
static int bi[N],t[N];
if(n==1){
b[0]=sqrt(a[0]);
return ;
}
Sqrt(a,b,(n+1)>>1);
Inv(b,bi,n);
copy(a,a+n,t);
int len=1;
while(len<n*2)
len<<=1;
fill(t+n,t+len,0);
fill(b+n,b+len,0);
fill(bi+n,bi+len,0);
NTT(t,len,1);
NTT(b,len,1);
NTT(bi,len,1);
for(int i=0;i<len;i++)
b[i]=1ll*(1ll*b[i]*b[i]+t[i]%mod)%mod*bi[i]%mod*inv2%mod;
NTT(b,len,-1);
fill(b+n,b+len,0);
}
牛顿迭代
推荐先看一下这篇文章(虽然似乎跟我们即将讲的关联不大…)
已知G(x),
,求F(x)
假设已经求出F_0(x)满足
那么就有
证明先咕了
多项式的ln
同时求导
所以就酱紫了
(常数项?不存在的)
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=(1e5+5)*4;
const int g=3;
const int mod=998244353;
void Debug(const int *p,char c,int n=8){
putchar(c);
for(int i=0;i<n;i++)
printf("%d ",p[i]);
puts("");
}
int ksm(int x,int y){
int res=1;
while(y){
if(y&1){
res=1ll*res*x%mod;
}
x=1ll*x*x%mod;
y>>=1;
}
return res;
}
void NTT(int *a,int n,int dir){
for(int i=1,j=0;i<n-1;i++){
for(int s=n;j^=s>>=1,~j&s;);
if(i<j)
swap(a[i],a[j]);
}
for(int m=1;m<n;m<<=1){
int m2=m<<1;
int w0=ksm(g,(mod-1)/m2);
if(dir==-1)
w0=ksm(w0,mod-2);
for(int i=0;i<n;i+=m2){
int w=1;
for(int j=0;j<m;j++){
int t0=a[i+j],t1=a[i+j+m];
a[i+j]=(t0+1ll*t1*w%mod)%mod;
a[i+j+m]=(t0-1ll*t1*w%mod+mod)%mod;
w=1ll*w*w0%mod;
}
}
}
if(dir==-1){
int inv=ksm(n,mod-2);
for(int i=0;i<n;i++)
a[i]=1ll*a[i]*inv%mod;
}
}
void Inv(const int *a,int *inv,int n){
static int t[N];
if(n==1){
inv[0]=ksm(a[0],mod-2);
return ;
}
Inv(a,inv,(n+1)/2);
int len=1;
while(len<2*n)
len<<=1;
copy(a,a+n,t);
fill(t+n,t+len,0);
fill(inv+n,inv+len,0);
NTT(t,len,1);
NTT(inv,len,1);
for(int i=0;i<len;i++)
inv[i]=1ll*inv[i]*((2-1ll*t[i]*inv[i]%mod+mod)%mod)%mod;
NTT(inv,len,-1);
fill(inv+n,inv+len,0);
}
void qiudao(const int *a,int *b,int n){
fill(b,b+n,0);
for(int i=1;i<n;i++)
b[i-1]=1ll*i*a[i]%mod;
}
void jifen(const int *a,int *b,int n){
fill(b,b+n,0);
for(int i=1;i<n;i++){
int inv=ksm(i,mod-2);
b[i]=1ll*a[i-1]*inv%mod;
}
}
void Mul(int *a,int *b,int len){
NTT(a,len,1);
NTT(b,len,1);
for(int i=0;i<len;i++)
a[i]=1ll*a[i]*b[i]%mod;
NTT(a,len,-1);
}
int n;
int a[N];
int b[N];
void Log(const int *a,int *b,int n){
static int ai[N],a1[N];
Inv(a,ai,n);
qiudao(a,a1,n);
int len=1;
while(len<n*2)
len<<=1;
fill(ai+n,ai+len,0);
fill(a1+n,a1+len,0);
Mul(a1,ai,len);
jifen(a1,b,n);
}
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%d",&a[i]);
Log(a,b,n);
for(int i=0;i<n;i++)
printf("%d ",b[i]);
}
多项式的exp
设
也就是说
由牛顿迭代法,有
void Exp(const int *a,int *b,int n){
static int t[N];
if(n==1){
b[0]=1;
return ;
}
Exp(a,b,(n+1)/2);
Ln(b,t,n);
for(int i=0;i<n;i++)
t[i]=a[i]-t[i];
t[0]++;
int len=1;
while(len<2*n)
len<<=1;
fill(b+n,b+len,0);
fill(t+n,t+len,0);
Mul(b,t,len);
}
多项式的快速幂
当然,更容易想到的方法是用多项式乘法和多项式取模,然后像正常整数快速幂那样去做
然而这里是另外一种方法
设
那么
于是我们求出ln(A(x)),乘上k,再exp即可
void Pow(const int *a,int *b,int n,int k){
static int t[N];
Ln(a,t,n);
for(int i=0;i<n;i++)
t[i]=1ll*t[i]*k%mod;
Exp(t,b,n);
}