【算法笔记】Lagrange 插值法
- 本文总计约 5000 字,阅读大约需要 30 分钟。
- 警告!警告!警告!本文有大量的 公式渲染,可能会导致加载异常缓慢!
前言
又一次没有前言,以后要不就不写前言了吧,怪麻烦的 QwQ。
题目引入
根据代数基本定理,我们知道 个横坐标互不相同的点可以唯一确定一个最高次项为 的多项式。例如:
点 和 就可以唯一确定多项式 ;点 可以唯一确定多项式 。
那么,给定任意 个点,你能计算出其所唯一确定的多项式 吗?其中 。
暴力怎么做
当然,我们依旧是要想一想,暴力怎么做?
假如这 个点分别为 ,那么我们可以定义其确定的多项式为 。
分别代入这 个点,得到一个方程:
当然,这个方程,我们就可以用 Gauss 消元法来解决,复杂度为 。
『哇啊,那这个复杂度太高了吧?能过 的数据吗?』
显然是不能的,所以,我们要做时间复杂度的优化。
Lagrange 插值
为了优化时间复杂度,著名的数学家 给出了一个更加高效的算法,即 Lagrange 插值法。
我们知道,对任意多项式 ,若 ,那么 。
因此,我们分别代入 ,就有了:
我们使用中国剩余定理,得到:
因此,有:
上述多项式就是 Lagrange 插值法的公式,通过这个公式,我们可以通过 个点值求出其唯一确定的多项式。
通过这个公式,求多项式的时间复杂度为 。
代码
这个算法在洛谷上的模板为 P4781【模板】拉格朗日插值,因为我们只需要求 的值,所以直接把 代入到上式中即可知:
于是就可以直接模拟上述公式了,代码如下:
#include <cstdio>
using namespace std;
typedef long long ll;
const ll MOD = 998244353;
const int MAXN = 5001;
int n;
ll x[MAXN], y[MAXN], k, ans;
ll inv(ll val) { //Fermat 小定理求逆元
val = (val % MOD + MOD) % MOD;
ll p = MOD - 2, res = 1;
while(p) {
if(p & 1) res = (res * val) % MOD;
val = (val * val) % MOD; p >>= 1;
}
return res;
}
int main(void) {
scanf("%d%lld", &n, &k);
for(int i = 1; i <= n; ++i)
scanf("%lld%lld", &x[i], &y[i]);
for(int i = 1; i <= n; ++i) { //插值主体
ll tmp = y[i];
for(int j = 1; j <= n; ++j) {
if(i == j) continue;
tmp = (tmp * ((k - x[j]) % MOD + MOD) % MOD * inv(x[i] - x[j])) % MOD;
}
ans = (ans + tmp) % MOD;
}
printf("%lld", ans);
return 0;
}
//by CaO
重心 Lagrange 插值
Lagrange 插值固然效率高,但我们发现其有一个缺点:不可维护。
所谓不可维护,就是如果我们在已知的点的基础上新增一些未知的点,那么我们就需要整个重新套用一次公式。
那么,单次重构的时间复杂度依旧为 。
『如果我要进行 次重构,那么总时间复杂度就是 了,如果 ,,那么时间复杂度就不够了!』
所以,单次重构的时间复杂度太高,那么我们就要想办法降低重构的复杂度。
我们手玩一下上面的那个式子:
我们可以把求积符号拆分:
在求和号里面同时乘 并除以 。就有:
注意到 是一个常数,提出来,则有:
定义:
原式即变为:
我们会发现,每次新插值时,维护 的复杂度为 的,维护 的总复杂度为 的。所以单次重构的时间复杂度为 。
这种算法被称为重心 Lagrange 插值法,代码留给读者作为练习(其实是我太菜了 QwQ)。
例题
本题目列表会持续更新。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】