多项式
正睿集训三道题考两道多项式,于是我决定补一补这个巨大的坑。
一、NTT
前置知识
阶
如果
性质:对于
原根
若
令
设
所以
显然
由此可以发现,单位根有的性质,原根都有,因此可以用原根代替单位根。
#include<iostream>
#include<cstdio>
#define int long long
using namespace std;
const int p=998244353,g=3,gi=332748118;
int n,m,lim,len,a[2100005],b[2100005],rev[2100005];
int f(int x,int y){
int s=x;x=1;
while(y){
if(y&1)
x=x*s%p;
s=s*s%p;y>>=1;
}
return x%p;
}
void ntt(int *a,int lim,int tmp){
for(int i=0;i<lim;++i)
if(i<rev[i])
swap(a[i],a[rev[i]]);
for(int mid=1;mid<lim;mid<<=1){
int wn;
if(tmp==1)
wn=f(g,(p-1)/(mid<<1));
else
wn=f(gi,(p-1)/(mid<<1));
for(int j=0;j<lim;j+=(mid<<1)){
int w=1;
for(int k=0;k<mid;++k,w=w*wn%p){
int x=a[j+k],y=w*a[j+k+mid]%p;
a[j+k]=(x+y)%p;
a[j+k+mid]=(x-y+p)%p;
}
}
}
if(tmp==-1){
int inv=f(lim,p-2);
for(int i=0;i<lim;++i)
a[i]=a[i]*inv%p;
}
}
signed main(){
scanf("%lld%lld",&n,&m);//这里的n和m是次数
for(int i=0;i<=n;++i)
scanf("%lld",&a[i]),a[i]=(a[i]+p)%p;
for(int i=0;i<=m;++i)
scanf("%lld",&b[i]),b[i]=(b[i]+p)%p;
lim=1;
while(lim<=n+m)
lim<<=1,++len;
for(int i=0;i<lim;++i)
rev[i]=(rev[i>>1]>>1)|((i&1)<<(len-1));//rev[i]表示i的二进制翻转,如rev[11001]=10011
ntt(a,lim,1);ntt(b,lim,1);
for(int i=0;i<lim;++i)
a[i]=a[i]*b[i]%p;
ntt(a,lim,-1);
for(int i=0;i<=n+m;++i)
printf("%lld ",a[i]);
return 0;
}
二、牛顿迭代法
已知多项式
结论:
当
假设已知
三、多项式求逆
已知多项式
假设我们已知
根据牛顿迭代法,有
#include<iostream>
#include<cstdio>
using namespace std;
const long long maxn=3e6+5,p=998244353,g=3,gi=332748118;//gi为原根的逆元
long long n,inv,a[2100005],r[2100005],b[2100005],c[2100005];
long long f(long long a,long long b){
long long s=a;a=1;
while(b){
if(b&1) a=a*s%p;
s=s*s%p;b>>=1;
}
return a%p;
}
void ntt(long long *a,long long lim,long long tmp){
for(int i=0;i<lim;++i)
if(i<r[i])
swap(a[i],a[r[i]]);
for(int mid=1;mid<lim;mid<<=1){
long long wn;
if(tmp==1)
wn=f(g,(p-1)/(mid<<1));
else
wn=f(gi,(p-1)/(mid<<1));
for(int j=0;j<lim;j+=(mid<<1)){
long long w=1;
for(int k=0;k<mid;++k,w=(w*wn)%p){
long long x=a[j+k],y=w*a[j+k+mid]%p;
a[j+k]=(x+y)%p;
a[j+k+mid]=(x-y+p)%p;
}
}
}
if(tmp==-1){
inv=f(lim,p-2);
for(int i=0;i<lim;++i)
a[i]=a[i]*inv%p;
}
}
void work(long long n,long long *a,long long *b){
if(n==1){
b[0]=f(a[0],p-2);
return ;
}
work((n+1)>>1,a,b);
long long lim=1,l=0;
while(lim<(n<<1))
lim<<=1,++l;
for(int i=0;i<lim;++i)
r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
for(int i=0;i<n;++i)
c[i]=a[i];
for(int i=n;i<lim;++i)
c[i]=0;
ntt(c,lim,1);ntt(b,lim,1);
for(int i=0;i<lim;++i)
b[i]=(2-c[i]*b[i]%p+p)%p*b[i]%p;//B=2B'-AB'^2
ntt(b,lim,-1);
for(int i=n;i<lim;++i)
b[i]=0;
}
int main(){
scanf("%lld",&n);
for(int i=0;i<n;++i)
scanf("%lld",&a[i]),a[i]=(a[i]+p)%p;
work(n,a,b);
for(int i=0;i<n;++i)
printf("%lld ",b[i]);
return 0;
}
四、多项式开方
已知多项式
假设我们已知
根据牛顿迭代法,有
这里有分式,套用多项式求逆即可。
#include<iostream>
#include<cstdio>
using namespace std;
const long long maxn=3e6+5,P=998244353,G=3,Gi=332748118,inv2=499122177;//gi为原根的逆元
long long n,m,inv,r[500005],c[500005],f[500005],g[500005],d[500005];
long long ginv[500005],gr[500005],fr[500005];
long long _pow(long long a,long long b){
long long s=a;a=1;
while(b){
if(b&1) a=a*s%P;
s=s*s%P;b>>=1;
}
return a%P;
}
void ntt(long long *a,long long lim,long long tmp){
for(int i=0;i<lim;++i)
if(i<r[i])
swap(a[i],a[r[i]]);
for(int mid=1;mid<lim;mid<<=1){
long long wn;
if(tmp==1)
wn=_pow(G,(P-1)/(mid<<1));
else
wn=_pow(Gi,(P-1)/(mid<<1));
for(int j=0;j<lim;j+=(mid<<1)){
long long w=1;
for(int k=0;k<mid;++k,w=(w*wn)%P){
long long x=a[j+k],y=w*a[j+k+mid]%P;
a[j+k]=(x+y)%P;
a[j+k+mid]=(x-y+P)%P;
}
}
}
if(tmp==-1){
inv=_pow(lim,P-2);
for(int i=0;i<lim;++i)
a[i]=a[i]*inv%P;
}
}
void finv(long long n,long long *a,long long *b){
if(n==1){
b[0]=_pow(a[0],P-2);
return ;
}
finv((n+1)>>1,a,b);
long long lim=1,l=0;
while(lim<(n<<1))
lim<<=1,++l;
for(int i=0;i<lim;++i)
r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
for(int i=0;i<n;++i)
c[i]=a[i];
for(int i=n;i<lim;++i)
c[i]=0;
ntt(c,lim,1);ntt(b,lim,1);
for(int i=0;i<lim;++i)
b[i]=(2-c[i]*b[i]%P+P)%P*b[i]%P;//B=2B'-AB'^2
ntt(b,lim,-1);
for(int i=n;i<lim;++i)
b[i]=0;
}
void ssqrt(long long n,long long *a,long long *b){
if(n==1){
b[0]=1;return ;
}
ssqrt((n+1)>>1,a,b);
long long lim=1,l=0;
while(lim<(n<<1))
lim<<=1,++l;
for(int i=0;i<lim;++i)
d[i]=0;
finv(n,b,d);
for(int i=0;i<n;++i)
c[i]=a[i];
for(int i=n;i<lim;++i)
c[i]=0;
for(int i=0;i<lim;++i)
r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
ntt(c,lim,1);ntt(d,lim,1);
for(int i=0;i<lim;++i)
c[i]=(c[i]*d[i])%P;//B=2B'-AB'^2
ntt(c,lim,-1);
for(int i=0;i<n;++i)
b[i]=(b[i]+c[i])%P*inv2%P;
for(int i=n;i<lim;++i)
b[i]=0;
}
int main(){
scanf("%lld",&n);
for(int i=0;i<n;++i)
scanf("%lld",&f[i]);
ssqrt(n,f,g);
for(int i=0;i<n;++i)
printf("%lld ",g[i]);
printf("\n");
return 0;
}
五、多项式ln(多项式对数函数)
已知多项式
可以先求出
#include<iostream>
#include<cstdio>
#define int long long
using namespace std;
const int mod=998244353,g=3,gi=332748118;
int n,lim,len,a[400005],b[400005],c[400005],r[400005];
int fpow(int x,int y){
int s=x;x=1;
while(y){
if(y&1)
x=x*s%mod;
y>>=1;s=s*s%mod;
}
return x%mod;
}
void ntt(int *a,int lim,int tmp){
for(int i=0;i<lim;++i)
if(i<r[i])
swap(a[i],a[r[i]]);
for(int mid=1;mid<lim;mid<<=1){
int wn;
if(tmp==1)
wn=fpow(g,(mod-1)/(mid<<1));
else
wn=fpow(gi,(mod-1)/(mid<<1));
for(int j=0;j<lim;j+=mid<<1){
int w=1;
for(int k=0;k<mid;++k,w=w*wn%mod){
int x=a[j+k],y=a[j+k+mid]*w%mod;
a[j+k]=(x+y)%mod;
a[j+k+mid]=(x-y+mod)%mod;
}
}
}
if(tmp==-1){
int inv=fpow(lim,mod-2);
for(int i=0;i<lim;++i)
a[i]=a[i]*inv%mod;
}
}
void work(int n,int *a,int *b){
if(n==1){
b[0]=fpow(a[0],mod-2);
return ;
}
work((n+1)>>1,a,b);
for(int i=0;i<n;++i)
c[i]=a[i];
for(int i=n;i<lim;++i)
c[i]=0;
ntt(c,lim,1);ntt(b,lim,1);
for(int i=0;i<lim;++i)
b[i]=(2-c[i]*b[i]%mod+mod)%mod*b[i]%mod;
ntt(b,lim,-1);
for(int i=n;i<lim;++i)
b[i]=0;
}
void mul(int n,int *a,int *b){
ntt(a,lim,1);ntt(b,lim,1);
for(int i=0;i<lim;++i)
a[i]=a[i]*b[i]%mod;
ntt(a,lim,-1);
for(int i=n;i<lim;++i)
a[i]=0;
}
void dx(int n,int *a,int *b){
b[n-1]=0;
for(int i=n-1;i>=1;--i)
b[i-1]=a[i]*i%mod;
}
void sum(int n,int *a,int *b){
b[0]=0;
for(int i=1;i<n;++i)
b[i]=fpow(i,mod-2)*a[i-1]%mod;
}
signed main(){
scanf("%lld",&n);
for(int i=0;i<n;++i)
scanf("%lld",&a[i]),a[i]=(a[i]+mod)%mod;
lim=1;len=0;
while(lim<(n<<1))
lim<<=1,++len;
for(int i=0;i<lim;++i)
r[i]=(r[i>>1]>>1)|((i&1)<<(len-1));
work(n,a,b);
dx(lim,a,c);
mul(n,b,c);
sum(n,b,c);
for(int i=0;i<n;++i)
printf("%lld ",c[i]);
printf("\n");
return 0;
}
六、多项式exp(多项式指数函数)
已知多项式
假设我们已知
根据牛顿迭代法,有
七、多项式幂函数
已知多项式
将等式两边取对数,得到:
因此只要先求一次对数,再将每一个系数乘以
加强版:不保证
找到第一个不为
void power(int n,int k,int k2,int *a,int *b){
int lim=1,len=0,inv1=1,pos=-1;
while(lim<(n<<1))
lim<<=1,++len;
for(int i=0;i<lim;++i)
r[i]=(r[i>>1]>>1)|((i&1)<<(len-1));
for(int i=0;i<n;++i){
if(a[i]!=0&&pos==-1)
inv1=fpow(a[i],mod-2),pos=i;
a[i]=a[i]*inv1%mod;
}
if(ok&&!a[0]){
for(int i=0;i<n;++i)
b[i]=0;
return ;
}
for(int i=0;i<n;++i){
if(i<n-pos) a[i]=a[i+pos];
else a[i]=0;
}
n-=pos;
ln(n,a,b);
for(int i=0;i<n;++i)
a[i]=b[i]*k%mod;
for(int i=0;i<lim;++i){
if(i>=n) a[i]=0;
b[i]=0;
}
exp(n,a,b);
n+=pos;
inv1=fpow(inv1,mod-2);
inv1=fpow(inv1,k2);
for(int i=n-1;i>=k*pos;--i)
b[i]=b[i-k*pos]*inv1%mod;
for(int i=0;i<min(k*pos,n);++i)
b[i]=0;
return ;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】