拉格朗日插值
Contents
拉格朗日插值
给定 $n$ 个点,我们可以确定唯一的最高 degree 为 $(n-1)$ 的多项式。
设多项式为 $f(x)$,第 $i$ 个点的坐标为 $(x_i,y_i)$,那么这个多项式在 $k$ 处的取值为:
$$f(k) = \sum\limits_{i=1}^ny_i \prod\limits_{i\neq j} \frac{k-x_j}{x_i-x_j}$$
时间复杂度:$O(n^2)$
拉格朗日插值板子
const int maxn = 2005;
int n;
ll k;
ll x[maxn], y[maxn];
ll qpow(ll a, ll b) {
ll res = 1;
while (b) {
if (b & 1)
res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
ll solve(ll k) {
ll ans = 0;
for (int i = 1; i <= n; i++) {
ll nu = 1, de = 1;
for (int j = 1; j <= n; j++) {
if (j == i) continue;
nu = nu * (k - x[j] + mod) % mod;
de = de * (x[i] - x[j] + mod) % mod;
}
de = qpow(de, mod-2);
ans = (ans + y[i] * nu % mod * de % mod) % mod;
}
return ans;
}
int main() {
cin >> n >> k;
for (int i = 1; i <= n; i++) cin >> x[i] >> y[i];
ll ans = solve(k);
cout << ans << endl;
}
$x$ 坐标为连续整数的拉格朗日插值
如果 $x$ 坐标是连续的整数,那么我们可以在 $O(n)$ 求出 $f(k)$。
我们假设 $x_i = i$,那么:
$$f(k) = \sum\limits_{i=1}^ny_i \prod\limits_{i\neq j} \frac{k-x_j}{x_i-x_j}$$
可以转化为:
$$f(k) = \sum\limits_{i=1}^ny_i \prod\limits_{i\neq j} \frac{k-j}{i-j}$$
分类讨论 $j < i$ 和 $j > i$ 的情况:
$$f(k) = \sum\limits_{i=1}^ny_i \prod\limits_{j=1}^{i-1} \frac{k-j}{i-j} \prod\limits_{j=i+1}^n \frac{k-j}{i-j}$$
所以可得:
$$f(k) = \sum\limits_{i=1}^ny_i \frac{1}{(i-1)!} (k-1)(k-2)…(k-(i-1)) \frac{1}{-1*-2*…*(-(n-i))} (k-i)(k-(i+1))…(k-n)$$
化简得到:
$$f(k) = \sum\limits_{i=1}^n \frac{y_i}{(-1)^{n-i} *i! * (n-i)!} \prod\limits_{j=1}^{i-1}(k-j) \prod\limits_{j=i+1}^n(k-j)$$
• 注意,后面这部分不能变成 $\frac{\prod\limits_{j=1}^{n}(k-j)}{k-i}$,因为我们无法确定 $k-i \neq 0$。
对于 $\prod\limits_{j=1}^{i-1}(k-j) \prod\limits_{j=i+1}^n(k-j)$,我们只要预处理出来一个前缀积和后缀积即可。
所以对于每一项(每个 $i$),我们都可以 $O(1)$ 时间算出对应值,总复杂度 $O(n)$。
重心拉格朗日插值
用于解决动态加点的问题。
利用重心拉格朗日插值,每加入一个新的点,我们可以在 $O(n)$ 时间内算出新的多项式。
$$f(k) = \sum\limits_{i=1}^ny_i \prod\limits_{i\neq j}^n \frac{k-x_j}{x_i-x_j}$$
我们设 $g(k) = \prod\limits_{i=1}^n(k-x_i)$,则有:
$$f(k) = g(k)\sum\limits_{i=1}^n \frac{1}{k-x_i} \prod\limits_{i\neq j}^n \frac{y_i}{x_i-x_j}$$
设 $t_i = \frac{y_i}{\prod\limits_{j \neq i}^n (x_i-x_j)}$,则有:
$$f(k) = g(k) \sum\limits_{i=1}^n \frac{t_i}{k-x_i}$$
所以每次添加一个新的点 $(x_{n+1}, y_{n+1})$ 时:
- 重新计算一下所有的 $t_i = t_i * \frac{1}{x_i - x_{n+1}}, i \in [1,n]$。
- 计算 $t_{n+1}$。
- 计算 $g(k)$。
• 注意,在求 $f(k)$ 时需要先判断一下是否有 $k=x_i$,有的话直接返回 $y_i$。(这个特判仅需要在重心拉格朗日插值中进行)。
重心拉格朗日插值板子
int Q;
ll x[maxn], y[maxn], t[maxn];
int n;
ll qpow(ll a, ll b) {
ll res = 1;
while (b) {
if (b & 1)
res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
ll solve(ll k) {
for (int i = 1; i <= n; i++) {
if (x[i] == k) // 需要特判是否 k = x[i]
return y[i];
}
ll ans = 0;
ll g = 1;
ll sum = 0;
for (int i = 1; i <= n; i++) {
g = g * (k - x[i] + mod) % mod;
sum = (sum + t[i] * qpow((k - x[i] + mod) % mod, mod - 2) % mod) % mod;
}
ans = g * sum % mod;
return ans;
}
int main() {
cin >> Q;
while (Q--) {
int op; cin >> op;
if (op == 1) { // 添加 (xx,yy) 的点
ll xx, yy;
cin >> xx >> yy;
n++;
x[n] = xx, y[n] = yy;
ll de = 1;
for (int j = 1; j < n; j++) {
de = de * (x[n] - x[j] + mod) % mod; // 更新 t[n]
t[j] = t[j] * qpow((x[j] - x[n] + mod) % mod, mod - 2) % mod;
}
de = qpow(de, mod - 2);
t[n] = y[n] * de % mod;
} else {
ll k; cin >> k; // 求 f(k)
cout << solve(k) << endl;
}
}
}
常用模型
- $\sum\limits_{i=1}^k i^n = 1^n+2^n+…k^n$ 是一个多项式 $f(k)$,其中 $deg(f)=n+1$(意味着需要 $n+2$ 个点进行插值)。
例1 CF622F The Sum of the k-th Powers
题意
给定 $k,n$,求:
$$\sum\limits_{i=1}^k i^n = 1^n+2^n+…k^n$$
其中,$0 \leq k \leq 10^9, 1 \leq n \leq 10^6$。
题解
上面说了这是一个多项式 $f(k)$,其中 $deg(f)=n+1$,意味着需要 $n+2$ 个点进行插值。
又因为 $x_i = i, y_i = \sum\limits_{j=1}^i j^n$,我们直接按照上述 $O(n)$ 的连续整数来插值即可。
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6+5;
ll qpow(ll a, ll b) {
ll res = 1;
while (b) {
if (b & 1)
res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
ll k,n;
ll fac[maxn], inv_fac[maxn], t[maxn];
ll y[maxn], pre[maxn], suf[maxn];
int main() {
fastio;
cin >> k >> n;
n += 2;
fac[0] = inv_fac[0] = 1;
pre[0] = suf[n+1] = 1;
for (ll i = 1; i <= n; i++) {
fac[i] = fac[i-1] * i % mod;
y[i] = (y[i-1] + qpow(i, n-2)) % mod;
pre[i] = pre[i-1] * (k - i + mod) % mod;
}
for (ll i = n; i >= 1; i--) suf[i] = suf[i+1] * (k - i + mod) % mod;
inv_fac[n] = qpow(fac[n], mod-2);
for (ll i = n-1; i >= 1; i--) {
inv_fac[i] = inv_fac[i+1] * (i+1) % mod;
}
ll ans = 0;
for (ll i = 1; i <= n; i++) {
int flag = ((n-i) & 1 ? -1 : 1);
ll res = y[i] % mod * inv_fac[i-1] % mod * inv_fac[n-i] % mod * pre[i-1] % mod * suf[i+1] % mod;
res = (res * flag + mod) % mod;
ans = (ans + res) % mod;
}
cout << ans << endl;
}