Loading

浅谈多项式

多项式全家桶(建设中)

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} \]

这么简短的公式还不能自己算吗

posted @ 2022-02-07 22:34  Quick_Kk  阅读(39)  评论(0编辑  收藏  举报