多项式乘法入门 FFT/NTT
首先要知道多项式是什么东西。多项式是形如
两个多项式相加:
两个多项式相减:
两个次数分别为
卷积:对于数列
点值表示法#
多项式
如
我们计算两个次数分别为
然后把这
接下来讲如何快速利用特殊的
复平面与单位根#
在复平面上,横轴表示实部,纵轴表示虚部,以原点为起点的向量即可表示一个复数。
单位根:定义
计算:
几个性质:
(自己画图就能明白)
快速傅里叶变换#
我们希望带入
对于多项式
显然,
带入
带入
然后我们发现带入两个值后,多项式
注意
这样可以直接分治,时间复杂度
快速傅里叶逆变换#
不要问我为什么,我们只需要把得到的点值对应位置相乘后,重新
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll maxn=4e6+10;
ll n,m;
const double pi=acos(-1.0);
struct Complex
{
double x,y;
Complex (double xx=0,double yy=0) {x=xx; y=yy;}
const Complex operator+(const Complex tmp) const
{
return (Complex){x+tmp.x,y+tmp.y};
}
const Complex operator-(const Complex tmp) const
{
return (Complex){x-tmp.x,y-tmp.y};
}
const Complex operator*(const Complex tmp) const
{
return (Complex){x*tmp.x-y*tmp.y,x*tmp.y+y*tmp.x};
}
}a[maxn],b[maxn],c[maxn];
void fft(ll n,Complex *a)
{
if(n==1) return;
Complex a1[n>>1], a2[n>>1];
ll m=n>>1;
for(ll i=0;i<m;i++) a1[i]=a[2*i];
for(ll i=0;i<m;i++) a2[i]=a[2*i+1];
fft(m,a1);
fft(m,a2);
Complex W(cos(1.0*pi/m),sin(1.0*pi/m)), w(1,0);
for(ll i=0;i<m;i++,w=w*W)
{
a[i]=a1[i]+w*a2[i];
a[i+m]=a1[i]-w*a2[i];
}
}
int main(){
scanf("%lld%lld",&n,&m);
for(ll i=0;i<=n;i++)
{
scanf("%lf",&a[i].x);
}
for(ll i=0;i<=m;i++)
{
scanf("%lf",&b[i].x);
}
ll s=n+m;
ll p=1;
while(p<=s) p<<=1;
fft(p,a);
fft(p,b);
for(ll i=0;i<p;i++) c[i]=a[i]*b[i];
fft(p,c);
reverse(c+1,c+p);
for(ll i=0;i<=s;i++) printf("%lld ",(ll)(c[i].x/p+0.5));
return 0;
}
递归转递推(蝴蝶变换)#
函数内开数组、递归…… 还有许多地方可以优化。
思考如何转递推。
我们尝试逐步模拟分类过程:
次数 | ||||||||
---|---|---|---|---|---|---|---|---|
次数 | ||||||||
次数 |
最后一行二进制分别为
反过来为
发现反过来是顺序的!
预处理
然后直接递推即可。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll maxn=4e6+10;
ll n,m,r[maxn];
const double pi=acos(-1);
struct Complex
{
double x,y;
Complex(double xx=0,double yy=0)
{
x=xx; y=yy;
}
const Complex operator+(const Complex tmp) const
{
return (Complex){x+tmp.x,y+tmp.y};
}
const Complex operator-(const Complex tmp) const
{
return (Complex){x-tmp.x,y-tmp.y};
}
const Complex operator*(const Complex tmp) const
{
return (Complex){x*tmp.x-y*tmp.y,x*tmp.y+y*tmp.x};
}
}a[maxn],b[maxn],c[maxn],tmp[maxn];
void fft(ll n,Complex *a)
{
for(ll i=0;i<(1<<n);i++)
if(i<r[i]) swap(a[i],a[r[i]]);
for(ll i=0;i<n;i++)
{
Complex W(cos(pi/(1<<i)),sin(pi/(1<<i)));
tmp[0]=(Complex){1,0};
for(ll j=1;j<(1<<i);j++) tmp[j]=tmp[j-1]*W;
for(ll j=0;j<(1<<n);j++)
if(!(j&(1<<i)))
{
Complex a1=a[j], a2=a[j+(1<<i)], t=a2*tmp[j&((1<<i)-1)];
a[j]=a1+t;
a[j+(1<<i)]=a1-t;
}
}
}
int main()
{
scanf("%lld%lld",&n,&m);
for(ll i=0;i<=n;i++)
{
scanf("%lf",&a[i].x);
}
for(ll i=0;i<=m;i++)
{
scanf("%lf",&b[i].x);
}
ll k=n+m, p=1;
while((1<<p)<=k) ++p;
for(ll i=1;i<(1<<p);i++)
r[i]=(r[i>>1]>>1)|((i&1)<<p-1);
fft(p,a);
fft(p,b);
for(ll i=0;i<(1<<p);i++) c[i]=a[i]*b[i];
fft(p,c);
reverse(c+1,c+(1<<p));
for(ll i=0;i<=k;i++) printf("%lld ",(ll)(c[i].x/(1<<p)+0.5));
return 0;
}
原根#
根据欧拉定理,如果
原根:对于互质的
我们把
A:
B:
我们容易得到类似于单位根的几个性质:
这和单位根不一模一样吗!!!只不过在模意义下而已。
模数限制#
通常情况下,模数为
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll maxn=4e6+10, mod=998244353, g=3;
ll n,m,a[maxn],b[maxn],c[maxn],r[maxn];
ll power(ll a,ll b)
{
ll ans=1;
while(b)
{
if(b&1) ans=ans*a%mod;
a=a*a%mod;
b>>=1;
}
return ans;
}
void ntt(ll n,ll *a)
{
for(ll i=0;i<(1<<n);i++)
if(i<r[i]) swap(a[i],a[r[i]]);
for(ll i=0;i<n;i++)
{
ll G=power(g,(mod-1)/(1<<i+1));
for(ll j=0;j<(1<<n);j+=(1<<i+1))
{
for(ll k=0,g=1;k<(1<<i);k++,g=g*G%mod)
{
ll a1=a[j+k], a2=a[(1<<i)+j+k], t=a2*g%mod;
a[j+k]=(a1+t)%mod;
a[(1<<i)+j+k]=(a1-t+mod)%mod;
}
}
}
}
int main()
{
scanf("%lld%lld",&n,&m);
for(ll i=0;i<=n;i++) scanf("%lld",a+i);
for(ll i=0;i<=m;i++) scanf("%lld",b+i);
ll s=n+m, p=0;
while((1<<p)<=s) ++p;
for(ll i=1;i<(1<<p);i++)
r[i]=(r[i>>1]>>1)|((i&1)<<p-1);
ntt(p,a);
ntt(p,b);
for(ll i=0;i<(1<<p);i++) c[i]=a[i]*b[i]%mod;
ntt(p,c);
reverse(c+1,c+(1<<p));
ll inv=power(1<<p,mod-2);
for(ll i=0;i<=s;i++) printf("%lld ",c[i]*inv%mod);
return 0;
}
分治
问题形式#
已知
分治求解#
考虑 cdq 分治,把
时间复杂度
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll maxn=4e5+10, mod=998244353, g=3;
ll n,a[maxn],f[maxn],rev[maxn],w1[maxn],w2[maxn],w[maxn];
ll power(ll a,ll b)
{
ll ans=1;
while(b)
{
if(b&1) ans=ans*a%mod;
a=a*a%mod;
b>>=1;
}
return ans;
}
void ntt(ll n,ll *a)
{
for(ll i=0;i<(1<<n);i++)
{
rev[i]=(rev[i>>1]>>1)|((i&1)<<n-1);
if(i<rev[i]) swap(a[i],a[rev[i]]);
}
for(ll i=0;i<n;i++)
{
ll G=power(g,(mod-1)/(1<<i+1));
for(ll j=0;j<(1<<n);j+=(1<<i+1))
{
for(ll k=0,g0=1;k<(1<<i);k++,g0=g0*G%mod)
{
ll a1=a[j+k], a2=a[(1<<i)+j+k], t=a2*g0%mod;
a[j+k]=(a1+t)%mod;
a[(1<<i)+j+k]=(a1-t+mod)%mod;
}
}
}
}
void cdq(ll l,ll r)
{
if(l==r) return;
ll mid=l+r>>1;
cdq(l,mid);
for(ll i=0;i<=r-l;i++) w1[i]=a[i];
for(ll i=0;i<=mid-l;i++) w2[i]=f[l+i];
ll s=r-l+mid-l, p=0;
while((1<<p)<=s) ++p;
ntt(p,w1);
ntt(p,w2);
for(ll i=0;i<(1<<p);i++) w[i]=w1[i]*w2[i]%mod;
ntt(p,w);
reverse(w+1,w+(1<<p));
ll inv=power(1<<p,mod-2);
for(ll i=mid+1;i<=r;i++)
{
f[i]=(f[i]+w[i-1-l]*inv)%mod;
}
for(ll i=0;i<(1<<p);i++) w1[i]=w2[i]=w[i]=0;
cdq(mid+1,r);
}
int main()
{
scanf("%lld",&n);
for(ll i=0;i<n-1;i++) scanf("%lld",a+i);
f[0]=1;
cdq(0,n-1);
for(ll i=0;i<n;i++) printf("%lld ",f[i]);
return 0;
}
出处:https://www.cnblogs.com/Sktn0089/p/17609803.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示