拉格朗日插值的简要学习,多项式的基础

# 概念

​ 对于一个 nn 次多项式 F(x)F(x) ,我们给 n+1n+1 个点的坐标 (xi,yi)(x_i,y_i) 可以唯一确定这个多项式。如果直接暴力解方程组,用高斯消元时间复杂度是 O(n3)O(n^3) ,有时难以接受。所以我们引入拉格朗日插值算法。

​ 我们构造 F(x)=i=0n(yi×j=0,jinxxjxixj)F(x)=\sum_{i=0}^{n} (y_i \times \prod_{j=0,j\ne i}^{n} \frac{x-x_j}{x_i-x_j}) 。可以发现,对于点 (xp,yp)(x_p,y_p) 来说,当 ipi \ne pjj 一定有一次会取到 pp ,此时后面的式子值为 00 ;当 i=pi=p 时,xxjxixj\frac{x-x_j}{x_i-x_j} 值恒为 11 ,所以值为 yiy_iypy_p ,所以有 F(xp)=ypF(x_p)=y_p 。所以 F(x)F(x) 为要求的多项式。如果要单点求 F(x)F(x) ,时间复杂度为 O(n2)O(n^2)


# luogu P4781【模板】拉格朗日插值

​ 给定 nn 个点,确定其多项式 F(x)F(x) ,求出F(k)mod998244353F(k) \; mod \; 998244353 的值 。1n2×1031\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,kn,k ,求 i=1nikmod109+7\sum _{i=1}^{n} i^k \; mod \;{10^9+7}1n1090k1061\le n\le 10^9 \;\; 0\le k\le 10^6


比较经典的一道题,首先可以证明原式是一个 k+1k+1 次多项式(证明较为复杂,此处略去)。我们设 F(x)=i=1xikF(x)=\sum_{i=1}^{x} i^k ,我们要求的就是 F(n)F(n) 。考虑拉格朗日插值,这里要用到一个常用的套路,如果题目中没有给定这 k+2k+2 个点,我们可以选取等差数列中连续的 k+2k+2 个点。选等差数列有什么好处呢,对于等差数列 aa ,我们令 an=a0+dna_n=a_0+d\cdot n ,其中 dd 为公差。我们将 (i,ai)(i,a_i) 代入到拉格朗日插值的公式中,可以得到

F(x)=i=0n(yai×j=0,jinxajd(ij))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=1d=1 ,得到 F(x)=i=0n(yi×j=0,jinxjij)F(x)=\sum_{i=0}^{n} (y_i \times \prod _{j=0 , j\ne i}^{n} {\frac{x-j}{i-j}}) 。我们继续化简,

j=0,jin(ij)=i(i1)(i2)1(1)(2)(in+1)(in)\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)

=i!×(ni)!×(1)ni\quad \quad\quad\quad \quad \quad \quad =i!\times (n-i)! \times (-1)^{n-i}

j=0,jin(xj)=j=0i1(xj)×j=i+1n(xj)\prod_{j=0, j\ne i}^{n} (x-j) = \prod_{j=0}^{i-1} (x-j) \times \prod_{j=i+1}^{n} (x-j)

所以我们只需要预处理阶乘的逆元以及 njn-j 的前缀积 prepre 和后缀积 sufsuf ,便可以 O(1)O(1) 求解后面的式子。然后我们再考虑 yi=F(i)y_i=F(i) ,我们可以先线性筛来预处理 iki^k ,然后再做一遍前缀和就可以得到所有的 yiy_i 。所以总时间复杂度为 O(k)O(k)

事实上这个式子比较常用,所以建议记住解决方法。若所求的值可以 O(n)O(n),就直接线性筛加前缀和,否则就利用拉格朗日插值求值。

更新于

请我喝[茶]~( ̄▽ ̄)~*

YC乌龙 微信支付

微信支付

YC乌龙 支付宝

支付宝

YC乌龙 贝宝

贝宝