文章目录
插值
告诉你一个函数会经过 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=0∑nℓi(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=i∏n(xi−xj)(x−xj)
这个多项式有个非常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=P2−P1
那么 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=0∏n(x−xi)
但是这样次数是 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}) (x−xi)的形式,证明这个形式次数大于 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=0∑naiℓ(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=0∑nyi(x−xi)j=0,i=j∏n(xi−xj)j=0∏n(x−xi)
当然,这样插值的前提是要求 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=0∏n(x−xi),wi<