拉格朗日插值的简要学习,多项式的基础
# 概念
对于一个 n 次多项式 F(x) ,我们给 n+1 个点的坐标 (xi,yi) 可以唯一确定这个多项式。如果直接暴力解方程组,用高斯消元时间复杂度是 O(n3) ,有时难以接受。所以我们引入拉格朗日插值算法。
我们构造 F(x)=∑i=0n(yi×∏j=0,j=inxi−xjx−xj) 。可以发现,对于点 (xp,yp) 来说,当 i=p 时 j 一定有一次会取到 p ,此时后面的式子值为 0 ;当 i=p 时,xi−xjx−xj 值恒为 1 ,所以值为 yi 即 yp ,所以有 F(xp)=yp 。所以 F(x) 为要求的多项式。如果要单点求 F(x) ,时间复杂度为 O(n2) 。
# luogu P4781【模板】拉格朗日插值
给定 n 个点,确定其多项式 F(x) ,求出F(k)mod998244353 的值 。1≤n≤2×103
模板题,代码如下
| #include <iostream> |
| using namespace std; |
| const int N=2e3+9,mod=998244353; |
| typedef long long int ll; |
| int n,k,x[N],y[N]; |
| ll ans; |
| inline int read() |
| { |
| int x=0,f=1;char c=getchar(); |
| while(c<'0' || c>'9') {if(c=='-') f=-1;c=getchar();} |
| while(c>='0' && c<='9') {x=x*10+c-48,c=getchar();} |
| return x*f; |
| } |
| ll quick_pow(ll x,int y) |
| { |
| ll ans=1; |
| while(y!=0) |
| { |
| if(y&1) ans=ans*x%mod; |
| x=x*x%mod; |
| y>>=1; |
| } |
| return ans; |
| } |
| int main() |
| { |
| n=read(),k=read(); |
| for(int i=1;i<=n;i++) x[i]=read(),y[i]=read(); |
| for(int i=1;i<=n;i++) |
| { |
| ll p=y[i],q=1; |
| for(int j=1;j<=n;j++) |
| { |
| if(i==j) continue; |
| p=p*(k-x[j])%mod,q=q*(x[i]-x[j])%mod; |
| } |
| ans=(ans+p*quick_pow(q,mod-2)%mod)%mod; |
| } |
| printf("%lld",(ans+mod)%mod); |
| return 0; |
| } |
# CF622F The Sum of the k-th Powers
给定 n,k ,求 ∑i=1nikmod109+7 。1≤n≤1090≤k≤106
比较经典的一道题,首先可以证明原式是一个 k+1 次多项式(证明较为复杂,此处略去)。我们设 F(x)=∑i=1xik ,我们要求的就是 F(n) 。考虑拉格朗日插值,这里要用到一个常用的套路,如果题目中没有给定这 k+2 个点,我们可以选取等差数列中连续的 k+2 个点。选等差数列有什么好处呢,对于等差数列 a ,我们令 an=a0+d⋅n ,其中 d 为公差。我们将 (i,ai) 代入到拉格朗日插值的公式中,可以得到
F(x)=∑i=0n(yai×∏j=0,j=ind⋅(i−j)x−aj) 。具体地,我们假设 d=1 ,得到 F(x)=∑i=0n(yi×∏j=0,j=ini−jx−j) 。我们继续化简,
j=0,j=i∏n(i−j)=i⋅(i−1)⋅(i−2)⋯1⋅(−1)⋅(−2)⋯(i−n+1)⋅(i−n)
=i!×(n−i)!×(−1)n−i
∏j=0,j=in(x−j)=∏j=0i−1(x−j)×∏j=i+1n(x−j)
所以我们只需要预处理阶乘的逆元以及 n−j 的前缀积 pre 和后缀积 suf ,便可以 O(1) 求解后面的式子。然后我们再考虑 yi=F(i) ,我们可以先线性筛来预处理 ik ,然后再做一遍前缀和就可以得到所有的 yi 。所以总时间复杂度为 O(k) 。
事实上这个式子比较常用,所以建议记住解决方法。若所求的值可以 O(n),就直接线性筛加前缀和,否则就利用拉格朗日插值求值。