【ybt金牌导航8-3-1】【luogu P4781】函数求值 / 【模板】拉格朗日插值

函数求值 / 【模板】拉格朗日插值

题目链接:ybt金牌导航8-3-1 / luogu P4781

题目大意

给定一个 n 项式上的 n 个点的坐标,然后问你这个多项式 y=f(k) 的 f(k) 值。

思路1

首先,我们考虑拉格朗日插值的朴素法。

那它是怎样的呢?
我们考虑构造 n 个函数,分别对应 n 个点,这些函数只在 x 值为对应点的 x 值时才为 1,如果在不是对应点的点,值就为 0
那怎么构造出这个函数呢?

可以这样,k 是这个函数的 x 值,x,y 是对应点的坐标。
w(i)=ijkxjxixj

因为如果 kxi,那你会发现,分数上下都是一样,那就都是 1,乘起来也是 1
那如果不是 xi,而且是在另一个点上,那它必然会存在一个 j,使得它的 x 坐标跟 j 的一样,那上面就变成了 0,那这个分数就变成了 0。有一个乘起来的变成了 0,那全部也会变成 0

那你看如何还原出那个多项式。
这里给出还原方法,其实很好理解:
f(k)=y1×w(1)+y2×w(2)+...+yn×w(n)

没错,就是每一个 w 函数都是觉得一个点的值为 1,然后乘上 y 值就是那个点的大小。这个时候其它点的 w 函数都是 0,那整个函数的值就是 y 值了。

然后就是两个枚举就好了。
f(k)=i=1n(yiijkxjxixj)

如果要还原出多项式,就要 n3,不是特别优秀。

代码1

#include<cstdio> #define ll long long #define mo 998244353 using namespace std; int n, k; ll x[2001], y[2001], ans, sum; ll ksm(ll x, ll y) {//求逆元的快速幂 ll re = 1; while (y) { if (y & 1ll) re = (re * x) % mo; x = (x * x) % mo; y >>= 1; } return re; } int main() { scanf("%d %d", &n, &k); for (int i = 1; i <= n; i++) { scanf("%lld %lld", &x[i], &y[i]); } for (int i = 1; i <= n; i++) { sum = y[i];//乘上的 y[i] for (int j = 1; j <= n; j++) if (i != j) { ll now = (k - x[j]) * ksm(x[i] - x[j], mo - 2) % mo;//最里层的 if (now < 0) now = ((now % mo) + mo) % mo;//可能值会小于0,要把它转回成正的 sum = (sum * now) % mo;//累乘 } ans = (ans + sum) % mo;//累加 } printf("%lld", ans); return 0; }

思路2

我们考虑弄一下上面的公式。

考虑到那个累乘的 ij 很烦,那我们这样,把累乘那里乘上 kxi,然后你的分子就可以整个脱离出来。
然后在前面乘的 yi 再除回去。

那就会变成这个:
f(k)=i=1n(kxi)i=1n(yikxiij1xixj)

那你如果要求多项式,你可以枚举 i,然后把 1kxi 拿到外面,然后每次就可以用总共的减去不需要的,就可以 O(n) 求前面的累乘和你提出来的。
然后其他的也很好求,就可以 O(n2) 求。

然后如果要多一个插入的点,你就把最里面的累乘多除以 xixn+1,然后就可以 O(n) 求累加的。然后左边的累乘简单一搞就好,那就可以 O(n) 插入。

而这个东西,就叫做重心拉格朗日插值法。

代码2

#include<cstdio> #define ll long long #define mo 998244353 using namespace std; int n, k; ll x[2001], y[2001], ans1, sum; ll ans2; ll ksm(ll x, ll y) {//快速幂求逆元 if (x < 0) x = (x % mo + mo) % mo; ll re = 1; while (y) { if (y & 1ll) re = (re * x) % mo; x = (x * x) % mo; y >>= 1; } return re; } int main() { scanf("%d %d", &n, &k); for (int i = 1; i <= n; i++) { scanf("%lld %lld", &x[i], &y[i]); } ans1 = 1; for (int i = 1; i <= n; i++) {//前面的累乘 ll now = k - x[i]; if (now < 0) now = (now % mo + mo) % mo; ans1 = (ans1 * now) % mo; } for (int i = 1; i <= n; i++) {//按着公式算 ll sum = (y[i] * ksm(k - x[i], mo - 2)) % mo; for (int j = 1; j <= n; j++) if (i != j) sum = (sum * ksm(x[i] - x[j], mo - 2)) % mo; ans2 = (ans2 + sum) % mo; } printf("%lld", (ans1 * ans2) % mo); return 0; }

__EOF__

本文作者あおいSakura
本文链接https://www.cnblogs.com/Sakura-TJH/p/YBT_JPDH_8-3-1.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   あおいSakura  阅读(63)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示