拉格朗日插值
概念
对于一个 \(n\) 次多项式 \(F(x)\) ,我们给 \(n+1\) 个点的坐标 \((x_i,y_i)\) 可以唯一确定这个多项式。如果直接暴力解方程组,用高斯消元时间复杂度是 \(O(n^3)\) ,有时难以接受。所以我们引入拉格朗日插值算法。
我们构造 \(F(x)=\sum_{i=0}^{n} (y_i \times \prod_{j=0且j\ne i}^{n} \frac{x-x_j}{x_i-x_j})\) 。可以发现,对于点 \((x_p,y_p)\) 来说,当 \(i \ne p\) 时 \(j\) 一定有一次会取到 \(p\) ,此时后面的式子值为 \(0\) ;当 \(i=p\) 时,\(\frac{x-x_j}{x_i-x_j}\) 值恒为 \(1\) ,所以值为 \(y_i\) 即 \(y_p\) ,所以有 \(F(x_p)=y_p\) 。所以 \(F(x)\) 为要求的多项式。如果要单点求 \(F(x)\) ,时间复杂度为 \(O(n^2)\) 。
luogu P4781【模板】拉格朗日插值
给定 \(n\) 个点,确定其多项式 \(F(x)\) ,求出\(F(k) \; mod \; 998244353\) 的值 。\(1\le n\le 2\times 10^3\)
模板题,代码如下
#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\) ,求 \(\sum _{i=1}^{n} i^k \; mod \;{10^9+7}\) 。\(1\le n\le 10^9 \;\; 0\le k\le 10^6\)
比较经典的一道题,首先可以证明原式是一个 \(k+1\) 次多项式(证明较为复杂,此处略去)。我们设 \(F(x)=\sum_{i=1}^{x} i^k\) ,我们要求的就是 \(F(n)\) 。考虑拉格朗日插值,这里要用到一个常用的套路,如果题目中没有给定这 \(k+2\) 个点,我们可以选取等差数列中连续的 \(k+2\) 个点。选等差数列有什么好处呢,对于等差数列 \(a\) ,我们令 \(a_n=a_0+d\cdot n\) ,其中 \(d\) 为公差。我们将 \((i,a_i)\) 代入到拉格朗日插值的公式中,可以得到
\(F(x)=\sum_{i=0}^{n} (y_{a_i} \times \prod _{j=0且j\ne i} ^{n} {\frac{x-a_j}{d\cdot (i-j)}})\) 。具体地,我们假设 \(d=1\) ,得到 \(F(x)=\sum_{i=0}^{n} (y_i \times \prod _{j=0 且 j\ne i}^{n} {\frac{x-j}{i-j}})\) 。我们继续化简,
$\prod _{j=0且 j\ne i}^{n} (i-j)=i \cdot (i-1)\cdot (i-2)\cdots 1\cdot (-1)\cdot (-2)\cdots (i-n+1)\cdot (i-n) $
\(\quad \quad\quad\quad \quad \quad \quad =i!\times (n-i)! \times (-1)^{n-i}\)
\(\prod _{j=0且 j\ne i}^{n} (x-j) = \prod_{j=0}^{i-1} (x-j) \times \prod_{j=i+1}^{n} (x-j)\)
所以我们只需要预处理阶乘的逆元以及 \(n-j\) 的前缀积 \(pre\) 和后缀积 \(suf\) ,便可以 \(O(1)\) 求解后面的式子。然后我们再考虑 \(y_i=F(i)\) ,我们可以先线性筛来预处理 \(i^k\) ,然后再做一遍前缀和就可以得到所有的 \(y_i\) 。所以总时间复杂度为 \(O(k)\) 。
事实上这个式子比较常用,所以建议记住解决方法。若所求的值可以 \(O(n)\),就直接线性筛加前缀和,否则就利用拉格朗日插值求值。