浅谈多项式
多项式全家桶(建设中)
FFT
用于两个多项式 \(O(nlogn)\) 快速相乘,思想为系数表示法化成点值表示法做乘法再化回系数表示法。
板子:(短但不算快)
#include<bits/stdc++.h>
using namespace std;
const int _=1e7+5;
const double Pi=acos(-1);
struct comp{double x,y;}a[_],b[_];
comp operator+(comp a,comp b){return {a.x+b.x,a.y+b.y};}
comp operator-(comp a,comp b){return {a.x-b.x,a.y-b.y};}
comp operator*(comp a,comp b){return {a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x};}
int n,m,K,N,id[_];
void fft(comp *A,int tp){
for(int i=0;i<N;++i) if(i<id[i]) swap(A[i],A[id[i]]);
for(int mid=1;mid<N;mid<<=1){
comp Wn={cos(Pi/mid),tp*sin(Pi/mid)};
for(int len=mid<<1,j=0;j<N;j+=len){
comp w={1,0};
for(int k=0;k<mid;++k,w=w*Wn){
comp x=A[j+k],y=w*A[j+mid+k];
A[j+k]=x+y,A[j+mid+k]=x-y;
}
}
}
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>m; K=max(ceil(log2(n+m+2)),1.0),N=pow(2,K);
for(int i=0;i<=n;++i) cin>>a[i].x;
for(int i=0;i<=m;++i) cin>>b[i].x;
for(int i=0;i<N;++i) id[i]=(id[i>>1]>>1)|((i&1)<<(K-1));
fft(a,1),fft(b,1);
for(int i=0;i<N;++i) a[i]=a[i]*b[i];
fft(a,-1);
for(int i=0;i<=n+m;++i) cout<<(int)(a[i].x/N+0.5)<<" ";
return 0;
}
NTT
思想类似FFT,将复数单位根换成原根(具有相同关键性质),避免了复杂的浮点数计算,常数小很多。
特别的,普通NTT只能用于都是整数且答案小于所取的有原根的模数。
模数一般取 \(998244353,1004535809,469762049\),这三个数的原根都是 \(3\)。
只快了0.5s,我是大常数选手。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int _=3e6+5,mod=998244353,g=3,ig=332748118;
int n,m,K,N,id[_];
ll a[_],b[_];
ll qpow(ll x,ll y,ll res=1){
for(;y;y>>=1){
if(y&1) res=res*x%mod;
x=x*x%mod;
}
return res;
}
void ntt(ll *A,int tp){
for(int i=0;i<N;++i) if(i<id[i]) swap(A[i],A[id[i]]);
for(int mid=1;mid<N;mid<<=1){
ll Wn=qpow(tp==1?g:ig,(mod-1)/(mid<<1));
for(int len=mid<<1,j=0;j<N;j+=len){
ll w=1;
for(int k=0;k<mid;++k,w=(w*Wn)%mod){
ll x=A[j+k],y=w*A[j+mid+k]%mod;
A[j+k]=(x+y)%mod,A[j+mid+k]=(x-y+mod)%mod;
}
}
}
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>m; K=max(ceil(log2(n+m+2)),1.0),N=pow(2,K);
for(int i=0;i<=n;++i) cin>>a[i],a[i]=(a[i]+mod)%mod;
for(int i=0;i<=m;++i) cin>>b[i],b[i]=(b[i]+mod)%mod;
for(int i=0;i<N;++i) id[i]=(id[i>>1]>>1)|((i&1)<<(K-1));
ntt(a,1),ntt(b,1);
for(int i=0;i<N;++i) a[i]=a[i]*b[i]%mod;
ntt(a,-1);
ll inv=qpow(N,mod-2);
for(int i=0;i<=n+m;++i) cout<<a[i]*inv%mod<<" ";
return 0;
}
拉格朗日插值
\(O(n^2)\)
假设该多项式为 \(f(x)\),第 \(i\) 个点的坐标为 \((x_i, y_i)\),我们需要找到该多项式在 \(k\) 点的取值,有:
\[f(k)=\sum\limits_{i=0}^{n} y_i \prod\limits_{i \ne j} \frac{k-x_j}{x_i-x_j}
\]
这么简短的公式还不能自己算吗