拉格朗日插值学习笔记
拉格朗日插值学习笔记
-
插值是什么
在离散数据的基础上补插连续函数,使得这条连续曲线通过全部给定的离散数据点。
插值是离散函数逼近的重要方法,利用它可通过函数在有限个点处的取值状况,估算出函数在其他点处的近似值。
come from baidu -
拉格朗日插值
根据
小学数学,我们知道\(n+1\)个横坐标不同的点可以确定唯一一个最高次为\(n\)的多项式。有些题目会直接或间接地给出这些点让我们求该多项式在某一位置时的值。思路一就是高斯消元\(O(n^3)\)直接求多项式系数,复杂度巨大。
思路二就是今天的主角——拉格朗日插值。
既然要求构造函数\(f(x)\)使其过\(P_1(x_1,y_1),P_2(x_2,y_2),···,P_n(x_n,y_n)\),那么就考虑构造\(n\)个函数\(f_1(x),f_2(x),···,f_n(x)\)使得第\(i\)个函数\(f_i(x)\)满足
\(f_i(x)=\begin{cases} y_i,if\quad x=x_i\\ 0,if\quad x=x_j(j\ne i) \end{cases}\)
则\(f(x)=\sum_{i=1}^nf_i(x)\)
先使其满足取值为\(0\)时的条件,可以设\(f_i(x)=a*\prod_{j\ne i}(x-x_j)\),再将\(P_i(x_i,y_i)\)代入,得
\[a=\frac{y_i}{\prod_{j\ne i}(x_i-x_j)} \]所以\(f_i(x)=y_i*\prod_{j\ne i}\frac{x-x_j}{x_i-x_j}\)
所以\(f(x)=\sum_{i=1}^ny_i*\prod_{j\ne i}\frac{x-x_j}{x_i-x_j}\)
可以在\(O(n^2)\)的时间实现
模板:拉格朗日插值
题目大意:\(由小学知识可知 n 个点 (x_i,y_i) 可以唯一地确定一个多项式 y=f(x)。现在,给定这 n 个点,请你确定这个多项式,并求出 f(k)\mod 998244353 的值。\)
solution:
代入上式\(n^2\)暴力即可code:
const int N = 2e3+10,mod = 998244353; int n,k,x[N],y[N]; inline int power(int a,int b,int mod){ int res = 1; while(b){ if(b&1) res = 1ll*res*a%mod; a = 1ll*a*a%mod; b >>= 1; } return res; } inline int get_val(int k){ int res = 0; for(int i = 1;i <= n; ++i){ int emm = 1; for(int j = 1;j <= n; ++j){ if(i == j) continue; emm = 1ll*emm*(k-x[j])%mod*power(x[i]-x[j],mod-2,mod)%mod; } res = (res+1ll*y[i]*emm%mod+mod)%mod; } return res; } //main read(n,k); for(int i = 1;i <= n; ++i) read(x[i],y[i]); write(get_val(k));拓展:横坐标是连续整数的拉格朗日插值
时间复杂度\(O(n)\)
若横坐标为连续整数,则上式为
\[f(x)=\sum_{i=1}^ny_i*\prod_{j\ne i}\frac{x-j}{i-j} \]前面的\(\sum\)自然难以化去,那么考虑如何快速求后面的\(\prod_{j\ne i}\frac{x-j}{i-j}\),将分子分母分开考虑。
-
分子:
维护前缀积和后缀积。即
\[P_i=\prod_{j=1}^{i}k-j \]\[S_i=\prod_{j=i}^nk-j \] -
分母:这不就是阶乘
用\[F_i=i! \]
so
\[f(k)=\sum_{j=1}^ny_i\frac{P_{i-1}*S_{i+1}}{F_i*F_{n-i}} \]这就完了吗?\(NO\)
我们发现,当\(n-i\)为奇数时,分母应该取负,so
原石原式应该等于\[f(k)=\sum_{j=1}^ny_i\frac{P_{i-1}*S_{i+1}}{(-1)^{n-i}F_i*F_{n-i}} \]\(done.\)
题目大意:
给定\(n,k\),求\(\sum_{i=1}^ni^k\),对\(10^9+7\)取模\(n\le 10^9,k\le 10^6\)
solution:
线性筛出\(1^i,2^i,···,(k+2)^i\)进行\(O(n)\)插值
\(code:\)
const int N = 1e6+10,mod = 1e9+7; int n,k,y[N],pre[N],suf[N],fac[N],inv[N]; vector<int> prime; bitset<N> pd; inline int power(int a,int b,int mod){ int res = 1; while(b){ if(b&1) res = 1ll*res*a%mod; a = 1ll*a*a%mod; b >>= 1; } return res; } inline void shai(int n){ y[1] = 1; for(int i = 2;i <= n; ++i){ if(!pd[i]) prime.emplace_back(i),y[i] = power(i,k,mod); for(auto j : prime){ if(i*j > n) break; pd[i*j] = true; y[i*j] = 1ll*y[i]*y[j]%mod; if(!(i%j)) break; } } for(int i = 2;i <= n; ++i) y[i] = (y[i-1]+y[i])%mod; } inline int get_val(int n){ pre[0] = suf[k+3] = 1; for(int i = 1;i <= k+2; ++i) pre[i] = 1ll*pre[i-1]*(n-i)%mod; for(int i = k+2;i >= 1; --i) suf[i] = 1ll*suf[i+1]*(n-i)%mod; fac[0] = inv[0] = fac[1] = inv[1] = 1; for(int i = 2;i <= k+2; ++i) fac[i] = 1ll*fac[i-1]*i%mod, inv[i] = 1ll*(mod-mod/i)*inv[mod%i]%mod; for(int i = 2;i <= k+2; ++i) inv[i] = 1ll*inv[i-1]*inv[i]%mod; int res = 0; for(int i = 1;i <= k+2; ++i){ int emm1 = 1ll*pre[i-1]*suf[i+1]%mod; int emm2 = 1ll*inv[i-1]*inv[k+2-i]%mod; int sgn = ((k+2-i)&1)?-1:1; res = (res+1ll*(emm2*sgn+mod)%mod*emm1%mod*y[i]%mod)%mod; } return res; } //main read(n,k); shai(k+2); if(n <= k+2) return write(y[n]),0; write(get_val(n)); -
-
重心拉格朗日插值
\(f(x)=\sum_{i=0}^ny_i*\prod_{j\ne i}\frac{x-x_j}{x_i-x_j}\)
\(let\quad g=\prod_{i=1}^nk-x_i\)
\[f(k)=g\sum_{i=0}^n\prod_{j\ne i}\frac{y_i}{(k-x_i)(x_i-x_j)} \]\(let\quad t_i=\frac{y_i}{\prod_{j\ne i}x_i-x_j}\)
\[f(k)=g\sum_{i=0}^n\frac{t_i}{k-x_i} \]每次加入新点后只需要计算\(t_i\)即可
好像大部分的拉插题都是横坐标是连续整数的
本文来自博客园,作者:CuFeO4,转载请注明原文链接:https://www.cnblogs.com/hzoi-Cu/p/18197807

浙公网安备 33010602011771号