学文化课学累了所以学个拉格朗日插值法来摸鱼

要求

给定 \(n\) 个不同的点,确定一条可以表示经过这 \(n\) 个点的线的 \(n - 1\) 次表达式。

分析

初中生都能想到,可以用待定系数法列出方程组,并转化为矩阵乘法的形式来求解。但是这种思路比较低效,使用高斯消元求出多项式的系数的时间复杂度是 \(O(n^3)\)

而拉格朗日插值法却可以在 \(O(n^2)\) 的时间复杂度求解系数。

公式

设拉格朗日基函数为

\[\ell_j(x) = \prod _{i = 0, i \ne j}^{n}\frac{x - x_i}{x_j - x_I} \]

所求多项式为

\[P(x) = \sum_{i = 0}^{n} y_i \ell_i(x) \]

解析

看不懂吗?别急。

先看一看基函数,看起来有点吓人不是吗?但事实上稍加观察后发现并不复杂。

基函数具有以下性质,或者说本来就是为了以下性质而构造:\(\ell_i(x_i) = 1\),且 \(\ell_i(x_j) = 0\),(\(i \ne j\))。即第 \(i\) 个拉格朗日基函数只在 \(x_i\) 处对 \(P(x)\) 贡献为 \(1\),在别点(\(x_j,\forall j \ne i\))对 \(P(x)\) 贡献为 \(0\),从而可以精准匹配所有给定数据点。

然后 \(P(x)\) 的工作原理就显而易见了。

于是我们在 \(O(n^2)\) 的时间复杂度求解出来了系数。

代码

#include <bits/stdc++.h>
#define ll long long

using namespace std;

const int mod = 998244353;
const int maxn = 2e3 + 5;
ll x[maxn], y[maxn];

ll binpow(ll x, ll y) {
    if (x == 1 || y == 0)
        return 1;

    ll res = 1;
    while (y) {
        if (y & 1)
            res = res * x % mod;
        x = x * x % mod;
        y = y >> 1;
    }

    return res;
}

int main() {
    int n, k; cin >> n >> k;
    for (int i = 1; i <= n; i ++) 
        cin >> x[i] >> y[i];
    
    ll ans = 0;
    for (int i = 1; i <= n; i ++) {
        ll up = 1, down = 1;
        
        for (int j = 1; j <= n; j ++) {
            if (i == j)
                continue;
            up = up * ((k - x[j] + mod) % mod) % mod;
            down = down * ((x[i] - x[j] + mod) % mod) % mod;
        }

        ll inv = binpow(down, mod - 2);
        ll point = y[i] * up % mod;
        point = point * inv % mod;
        
        ans = ans + point % mod;

    }

    cout << ans % mod << '\n';
    return 0;
}

拓展

当取值为连续时,我们可以在 \(O(n)\) 的时间复杂度求解问题,更加高效。

但是我懒了,来日再写。

posted @ 2025-05-23 23:06  QianXiquq  阅读(61)  评论(0)    收藏  举报