[整理]多项式多点求值/快速插值
0.多点求值
描述:给定 \(n\) 阶多项式 \(f(x)\),求其 \(m\) 个点值 \(f(a_1),\dots,f(a_m)\)。
乍一看这个东西似乎是不太可做的,我们先考虑如何缩小问题规模也就是多项式阶数。
根据因式定理我们知道一个被 \((x-a)\) 整除的多项式在 \(a\) 处的点值为 \(0\),考虑通过这种方式转化问题。
接下来就比较妙,我们将点值分成左右两半,然后设 \(g_0(x)=\prod\limits_{i=1}^{\lfloor n/2\rfloor}(x-a_i)\),右半边同理设为 \(g_1(x)\)。然后我们发现显然这个东西带入属于沓那一边后都会得到 \(0\),那么一个简单的想法就是直接把 \(f\) 对沓取模,我们以左边为例。
设 \(f(x)=q(x)g_0(x)+r(x)\),沓带入左边的点值会得到 \(r(x)\),而 \(r(x)\) 的次数一定至少减半,所以我们成功缩小了问题规模。
每一步的 \(g_0\) 和 \(g_1\) 可以预先分治处理出来。
由于自带大常数,所以为了卡过板子题采取了小范围暴力的方法,代码可能会很难看:
(略去了多项式板子,可以去另一篇博客里看)
int n,m,F[N],a[N],b[N],*G[N],siz[N];
void Build(int k,int l,int r){//类似线段树建树从下往上合并
if(r-l<=50){
siz[k]=r-l+1,G[k]=new int[siz[k]+1],G[k][0]=1;
for(int i=l;i<=r;i++){
for(int j=i-l+1;~j;j--)G[k][j+1]=G[k][j];
G[k][0]=0;
for(int j=0;j<=i-l+1;j++){
(G[k][j]+=p-(LL)G[k][j+1]*a[i]%p)%=p;
}
}
return;
}
Build(ls,l,nmid),Build(rs,nmid+1,r);
siz[k]=siz[ls]+siz[rs],G[k]=new int[siz[k]+1];
Mul(G[ls],G[rs],G[k],siz[ls],siz[rs]);
}
int Q[N];
void Solve(int k,int l,int r,int *f){
if(r-l<=500){//卡常,经测试大概500左右暴力最快
for(int i=l;i<=r;i++){
int res=0;
for(int j=siz[k]-1;~j;j--){
res=((LL)res*a[i]%p+f[j])%p;
}
b[i]=res;
}
return;
}
int R[siz[k]+2<<1];
Divide(f,G[ls],siz[k]-1,siz[ls],Q,R);
Solve(ls,l,nmid,R);
Divide(f,G[rs],siz[k]-1,siz[rs],Q,R);
Solve(rs,nmid+1,r,R);
}
void Print(int x){
if(x>9)Print(x/10);
putchar(x%10+48);
}
int main(){
Read(n),Read(m);
for(int i=0;i<=n;i++)Read(F[i]);
for(int i=1;i<=m;i++)Read(a[i]);
Build(1,1,m);
if(n>=m)Divide(F,G[1],n,m,Q,F);
int st=clock();
Solve(1,1,m,F);
for(int i=1;i<=m;i++)Print(b[i]),putchar('\n');
cerr<<(dv)(clock()-st)/CLOCKS_PER_SEC<<endl;
KafuuChino HotoKokoa
}
转置原理的做法会在之后的某个时间补上
1.快速插值
描述:给定 \(n\) 个点值 \((x_1,y_1),\dots,(x_n,y_n)\),插值出 \(n-1\) 次多项式 \(f(x)\)。
首先搬出来拉格朗日插值公式 \(f(x)=\sum\limits_{i=1}^ny_i\prod\limits_{i\ne j}\dfrac{x-x_j}{x_i-x_j}\),然而沓显然是不能直接用的。
我们先看一下那一坨 \(x_i-x_j\) 的乘积,沓就等于全部的乘积(设为 \(g(x)\))比上去除的乘积 \(\dfrac{\prod_{i=1}^n(x_i-x_j)}{x_i-x_i}\)。咦,变成未定式了,我们洛必达一下发现它变成了 \(g'(x_i)\),于是原式变成了 \(f(x)=\sum\limits_{i=1}^n\dfrac{y_i}{g'(x_i)}\prod\limits_{i\ne j}(x-x_j)\)。
还是不能求,这时候我们应用分治,以 \(f_{l,r}(x)\) 表示区间 \([l,r]\) 内点的插值结果,稍微玩一下式子,某半边的乘积对另一半的贡献是完整的。
所以我们就可以分治解决了,复杂度和多点求值相同。该用到的多项式都可以像多点求值里那样分治求出来,然后对 \(g'(x)\) 做多点求值。
代码先不贴了,等卡过常或改进了多点求值再贴,反正理解了原理后代码很好写(