各种神奇的插值法(坑)

这篇博客介绍了多项式插值的几种方法,包括拉格朗日插值法的普通形式和重心型,以及牛顿插值法。博主讨论了各种方法的优缺点,如拉格朗日在计算中的繁琐和牛顿插值法在增加点时的便利性。文章还提及了泰勒插值和多项式快速插值,但并未深入探讨。重点在于插值的理论和计算复杂度分析。
摘要由CSDN通过智能技术生成

插值

告诉你一个函数会经过 n n n 个点( n n n个点各不相同),然后让你计算其余几个位置的取值。(应该吧,个人理解)

一般情况下可能会用在一些数据统计中函数的拟合。(不然为什么会有这么多乱七八糟的拟合啊QAQ)

当然,这里主要涉及的是多项式插值,即利用经过这 n n n个点的最高次项次数小于 n n n的关于 x x x的那个多项式,通过代入或者其他方法求出这几个位置的取值。

当然,这里给出一道模板题,在拉格朗日插值和牛顿插值时就是用这个模板题的。

模板题

当然,这种题目暴力用高斯消元也是能做的,可惜A不了,毕竟时间复杂度时 O ( n 3 ) O(n^3) O(n3)

下文都认为给出的是 n + 1 n+1 n+1个点,点的标号从 0 0 0开始,同时设第 k k k个点为 ( x k , y k ) (x_{k},y_{k}) (xk,yk)

拉格朗日插值法

普通拉格朗日插值法

观察模板题和高斯消元,你会发现我们把这个多项式解出来真的太浪费啦。

有没有不用求出多项式也能求值的方法呢?

有!

拉格朗日发表了这么一个方法:

L ( x ) = ∑ i = 0 n ℓ i ( x ) y i L(x)=\sum\limits_{i=0}^nℓ_{i}(x)y_{i} L(x)=i=0ni(x)yi

其中 ℓ i ( x ) ℓ_{i}(x) i(x)叫 拉格朗日基本多项式 , L ( n ) L(n) L(n)叫 拉格朗日插值多项式 。

ℓ i ( x ) = ∏ j = 0 , j ≠ i n ( x − x j ) ( x i − x j ) ℓ_{i}(x)=\prod\limits_{j=0,j≠i}^n\frac{(x-x_{j})}{(x_{i}-x_{j})} i(x)=j=0,j=in(xixj)(xxj)

这个多项式有个非常NB的性质(其实也非常显然)

就是 ℓ i ( x ) ℓ_{i}(x) i(x) x j ( j ≠ i ) x_{j}(j≠i) xj(j=i)处为 0 0 0,在 x i x_{i} xi处为 1 1 1

那么显然, L ( x ) L(x) L(x)经过这 n n n个点。

这样,我们就只需要把 k k k代入,就可以在 O ( n 2 ) O(n^2) O(n2)的时间内求解了。

显然,我们节省的是求出这个多项式的时间。

当然,可以证明的是,这个拉格朗日插值多项式是唯一一个次数≤ n n n的经过这 n + 1 n+1 n+1个点的多项式。

唯一性

假设存在两个 n n n次多项式,都经过这 n + 1 n+1 n+1个点,假设这两个多项式为 P 1 , P 2 P_1,P_2 P1,P2

P 3 = P 2 − P 1 P_3=P_2-P_1 P3=P2P1

那么 P 3 P_3 P3显然 ≠ 0 ≠0 =0

而且因为都经过 n + 1 n+1 n+1个点,所以有 n + 1 n+1 n+1个根,所以 P 3 P_3 P3的次数为 n n n

而且可以写成

那么 P 3 P_{3} P3可以写成 k ∏ i = 0 n ( x − x i ) k\prod\limits_{i=0}^n(x-x_i) ki=0n(xxi)

但是这样次数是 n + 1 n+1 n+1的,显然不对,矛盾,证毕。

所以最多存在这样唯一一个多项式。

存在性

首先,不一定存在次数为 n n n 的多项式,举个例子:

( 1 , 1 ) , ( 2 , 2 ) , ( 3 , 3 ) (1,1),(2,2),(3,3) (1,1),(2,2),(3,3)就不能被一个二次方程经过。

当然,能经过这 n + 1 n+1 n+1个点的也不一定要是个次数大于 0 0 0的多项式,比如你给 n + 1 n+1 n+1 y y y值相等的点,怎么可能存在一个 n n n次多项式能够经过 n + 1 n+1 n+1 y y y值相同的点啊(因为这和一个多项式能有 n + 1 n+1 n+1个点的命题是等价的,这个命题先让错误,不然可以写成 ( x − x i ) (x-x_{i}) (xxi)的形式,证明这个形式次数大于 n n n)。

所以下面假定至少存在两个点 y y y 值不同。

在这个条件下,我们可以证明 L ( x ) L(x) L(x)是一个次数大于 0 0 0的多项式。

我们假设存在一组 a 0 , a 1 , a 2 , . . . a n + 1 a_0,a_1,a_2,...a_{n+1} a0,a1,a2,...an+1系数,使得:

P ( x ) = ∑ i = 0 n a i ℓ ( i ) = a n + 1 P(x)=\sum\limits_{i=0}^na_{i}ℓ(i)=a_{n+1} P(x)=i=0nai(i)=an+1

首先,因为 ℓ i ( x ) ℓ_{i}(x) i(x)函数只有在 x i x_{i} xi处为 1 1 1,其余 x j x_{j} xj处都是为 0 0 0的,所以显然 P ( x i ) = a i P(x_{i})=a_{i} P(xi)=ai,但是呢,这个函数的值又是恒定的,所以 a 0 = a 1 = a 2 = . . . . = a n + 1 a_{0}=a_{1}=a_{2}=....=a_{n+1} a0=a1=a2=....=an+1

又因为 y i y_{i} yi并不相同,所以证毕。

代码

当然,这道题目我还是有🐎代码的。

时间复杂度 O ( n 2 ) O(n^2) O(n2)

#include<cstdio>
#include<cstring>
#define  N  2100
using  namespace  std;
typedef  long  long  LL;
const  LL  mod=998244353;
inline  LL  ksm(LL  x,LL  y)
{
   
	x%=mod;
	LL  ans=1;
	while(y)
	{
   
		if(y&1)ans=ans*x%mod;
		x=x*x%mod;y>>=1;
	}
	return  ans;
}
LL  n,k;
LL  xx[N],yy[N];
void  calc()
{
   
	LL  ans=0;
	for(int  i=0;i<=n;i++)
	{
   
		LL  shit=1;
		for(int  j=0;j<=n;j++)if(i!=j)shit=(k-xx[j]+mod)*ksm(xx[i]-xx[j]+mod,mod-2)%mod*shit%mod;
		ans=(shit*yy[i]+ans)%mod;
	}
	printf("%lld\n",ans);
}
int  main()
{
   
	scanf("%lld%lld",&n,&k);n--;
	for(int  i=0;i<=n;i++)scanf("%lld%lld",&xx[i],&yy[i]);
	calc();
	return  0;
}

当然,这样🐎是 O ( n 2 log ⁡ n ) O(n^2\log{n}) O(n2logn)的,改进的方法也比较简单,分母乘起来最后求逆元就行了。

这样就可以到严格的 O ( n 2 ) O(n^2) O(n2)了。

优点与缺点

这里直接照搬https://www.cnblogs.com/ECJTUACM-873284962/p/6833391.html的,因为我自己根本就不了解插值,OI中也基本上不会去处理数据拟合,下面是OI的貌似也不太需要的亚子。

拉格朗日插值法的公式结构整齐紧凑,在理论分析中十分方便,然而在计算中,当插值点增加或减少一个时,所对应的基本多项式就需要全部重新计算,于是整个公式都会变化,非常繁琐。这时可以用重心拉格朗日插值法或牛顿插值法来代替。此外,当插值点比较多的时候,拉格朗日插值多项式的次数可能会很高,因此具有数值不稳定的特点,也就是说尽管在已知的几个点取到给定的数值,但在附近却会和“实际上”的值之间有很大的偏差(如右下图)。这类现象也被称为龙格现象,解决的办法是分段用较低次数的插值多项式。

连续情况

当然,在给定的取值是连续的情况下(即等差数列),可以做到 O ( n ) O(n) O(n)的插值。(当然,前提是你得花 n log ⁡ n n\log{n} nlogn检验其是否是等差数列,当然,有时候是已知条件)

你可以通过函数的缩放,把 x x x转变成: x i = i x_{i}=i xi=i的情况。

至于怎么做,照搬你谷日报:
我们可以用
而且,如果是在模运算的情况下,只要 O ( n ) O(n) O(n)预处理逆元,也一样可以办到 O ( n ) O(n) O(n)

代码:

//你谷日报的代码
//当x_i=i时求L_n(k) 
double L_n_k=0;
for (int i=1;i<=n;i++)
    if ((n-i)%2) L_n_k+=y[i]*((pre[i-1]*suf[i+1])/(-fac[i]*fac[n-i]));
    else L_n_k+=y[i]*((pre[i-1]*suf[i+1])/(fac[i]*fac[n-i]));

重心型拉格朗日插值法

Ⅰ型

总所周知,普通拉格朗日插值法在新增加一个点的时候就需要重新 O ( n 2 ) O(n^2) O(n2)计算一下。(雾

不是,为什么啊(・∀・(・∀・(・∀・*),难道不是只要把 ℓ i ( x ) ℓ_{i}(x) i(x)用数组存起来,计算不也是轻轻松松的事吗,当然,如果不用模运算的话时间复杂度是 O ( n ) O(n) O(n),如果要用模运算,计算逆元的时间就比较久了,会到达 O ( n log ⁡ n ) O(n\log{n}) O(nlogn)

如果是要另外求个点,求先把分子求出来,然后不断逆元乘法也能解决,复杂度跟上面同理。

为什么要 O ( n 2 ) O(n^2) O(n2)不过我没有去打代码验证

但是仔细想想上述的做法如果处理不好还是有一定弊端的,如果要另外求个点,而且是浮点数运算的话,这种做法可能掉精比较厉害,严重的可能精度直接起飞了,不过一般去到 1 0 3 10^3 103一般也会加 m o d    \mod{} mod运算吧,不过也很好解决,只要处理出现在分子和新的分子的比值再乘,那么掉精问题估计也就没有那么严重了。

额,认真的讲一下你谷博客中的 Ⅰ Ⅰ 型吧。

如果我们改变一下上述的式子:

L ( n ) = ∑ i = 0 n y i ∏ j = 0 n ( x − x i ) ( x − x i ) ∏ j = 0 , i ≠ j n ( x i − x j ) L(n)=\sum\limits_{i=0}^ny_{i}\frac{\prod\limits_{j=0}^n(x-x_{i})}{(x-x_{i})\prod\limits_{j=0,i≠j}^n(x_{i}-x_{j})} L(n)=i=0nyi(xxi)j=0,i=jn(xixj)j=0n(xxi)

当然,这样插值的前提是要求 x ≠ x i x≠x_{i} x=xi,否则式子就爆炸了啊。

ℓ ( x ) = ∏ i = 0 n ( x − x i ) , w i = y i ∏ j = 0 , i ≠ j n ( x i − x j ) ℓ(x)=\prod\limits_{i=0}^n(x-x_{i}),w_{i}=\frac{y_{i}}{\prod\limits_{j=0,i≠j}^n(x_{i}-x_{j})} (x)=i=0n(xxi),wi<

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值