拉格朗日插值学习笔记
拉格朗日插值学习笔记
简介
在 2022 年联合省选 D1T2 吃了这个的亏,来补一补。虽说我压根就没看出来跟插值有啥关系。
我们都知道, 个点的点值 可以唯一确定一个 次多项式(为了叙述方便,本文中所有“ 次多项式”“ 次函数”的最高次项系数可以为 )。
拉格朗日插值就是用来求这个多项式的。
例子
例如,我们已知四个点值 ,要求过这四个点的三次函数 。
当然,你可以直接待定系数用高斯消元解方程,但那是 的,拉格朗日插值可以在 内求解。
约瑟夫·拉格朗日认为这个函数可以用四个三次函数线性组合出来。
首先构造一个三次函数 ,在 的取值为 ,但在其他三个点的取值为 。
类似地,构造 依次在每个点取值为 ,在其他三个点取值为 。
画到一张图里就是这样:
那么这几个函数有啥用呢?
容易发现,,把那四个点点值代入进去就可以知道。
现在问题就转化为了怎么求 。
类似于一次函数的两点式:
我们可以构造函数:
于是就做完了。
推导
是时候写一下推导了。
对于 个点值求 次多项式的问题,我们先有点值 (),设函数 (),它们是 次函数,且满足:
则 可以写成:
最终得到:
代码
题目是 洛谷 P4781 拉格朗日插值
套上面公式直接算即可。
如果你在计算每个 的时候都求一遍逆元,会导致复杂度变为 ,多带一个逆元的 。为了让复杂度瓶颈不在逆元上,我们通常分开算分子分母,在每个函数算完后再进行有理数取模,这样的复杂度为 。
//By: Luogu@rui_er(122461)
#include <bits/stdc++.h>
#define rep(x,y,z) for(int x=y;x<=z;x++)
#define per(x,y,z) for(int x=y;x>=z;x--)
#define debug printf("Running %s on line %d...\n",__FUNCTION__,__LINE__)
#define fileIO(s) do{freopen(s".in","r",stdin);freopen(s".out","w",stdout);}while(false)
using namespace std;
typedef long long ll;
const int N = 2e3+5, mod = 998244353;
int n, k, x[N], y[N], ans;
template<typename T> void chkmin(T& x, T y) {if(x > y) x = y;}
template<typename T> void chkmax(T& x, T y) {if(x < y) x = y;}
int qpow(int x, int y) {
int ans = 1;
for(;y;y>>=1,x=1LL*x*x%mod) if(y & 1) ans = 1LL * ans * x % mod;
return ans;
}
int inv(int x) {return qpow(x, mod-2);}
int main() {
scanf("%d%d", &n, &k);
rep(i, 1, n) scanf("%d%d", &x[i], &y[i]);
rep(i, 1, n) {
int p = y[i], q = 1;
rep(j, 1, n) {
if(i != j) {
p = 1LL * p * (k - x[j]) % mod;
q = 1LL * q * (x[i] - x[j]) % mod;
}
}
ans = (ans + 1LL * p * inv(q) % mod + mod) % mod;
}
printf("%d\n", ans);
return 0;
}
连续点值的插值
如果已知的点值是连续点的点值,我们可以做到 的插值。
有时候发现了一个函数是 次多项式,就求 个点值进去插值。为了省事,这里我们令 ()。注意这里 与上面意义不一样,是次数而不是点数。
我们有拉插公式:
代入 :
考虑怎么快速求 。
这个东西的分子是:
分母的话把 累乘拆成两段阶乘,就是:
于是连续点值的插值公式:
预处理前后缀积、阶乘阶乘逆然后代这个式子的复杂度为 。
例子是 CodeForces 622F The Sum of the k-th Powers。
可以发现答案是 次多项式,因此代 个点进去拉插。
本题的 可以线性筛,因此复杂度可以做到 。
//By: Luogu@rui_er(122461)
#include <bits/stdc++.h>
#define rep(x,y,z) for(int x=y;x<=z;x++)
#define per(x,y,z) for(int x=y;x>=z;x--)
#define debug printf("Running %s on line %d...\n",__FUNCTION__,__LINE__)
#define fileIO(s) do{freopen(s".in","r",stdin);freopen(s".out","w",stdout);}while(false)
using namespace std;
typedef long long ll;
const int N = 1e6+5, mod = 1e9+7;
int n, k, tab[N], p[N], pcnt, f[N], pre[N], suf[N], fac[N], inv[N], ans;
template<typename T> void chkmin(T& x, T y) {if(x > y) x = y;}
template<typename T> void chkmax(T& x, T y) {if(x < y) x = y;}
int qpow(int x, int y) {
int ans = 1;
for(;y;y>>=1,x=1LL*x*x%mod) if(y & 1) ans = 1LL * ans * x % mod;
return ans;
}
void sieve(int lim) {
f[1] = 1;
rep(i, 2, lim) {
if(!tab[i]) {
p[++pcnt] = i;
f[i] = qpow(i, k);
}
for(int j=1;j<=pcnt&&1LL*i*p[j]<=lim;j++) {
tab[i*p[j]] = 1;
f[i*p[j]] = 1LL * f[i] * f[p[j]] % mod;
if(!(i % p[j])) break;
}
}
rep(i, 2, lim) f[i] = (f[i-1] + f[i]) % mod;
}
int main() {
scanf("%d%d", &n, &k);
sieve(k+2);
if(n <= k + 2) return printf("%d\n", f[n])&0;
pre[0] = suf[k+3] = 1;
rep(i, 1, k+2) pre[i] = 1LL * pre[i-1] * (n - i) % mod;
per(i, k+2, 1) suf[i] = 1LL * suf[i+1] * (n - i) % mod;
fac[0] = inv[0] = fac[1] = inv[1] = 1;
rep(i, 2, k+2) {
fac[i] = 1LL * fac[i-1] * i % mod;
inv[i] = 1LL * (mod - mod / i) * inv[mod%i] % mod;
}
rep(i, 2, k+2) inv[i] = 1LL * inv[i-1] * inv[i] % mod;
rep(i, 1, k+2) {
int P = 1LL * pre[i-1] * suf[i+1] % mod;
int Q = 1LL * inv[i-1] * inv[k+2-i] % mod;
int mul = ((k + 2 - i) & 1) ? -1 : 1;
ans = (ans + 1LL * (Q * mul + mod) % mod * P % mod * f[i] % mod) % mod;
}
printf("%d\n", ans);
return 0;
}
重心拉格朗日插值
不会。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示