Lagrange 補間

$x_0, \ldots, x_d$ が互いに異なるとき、点 $(x_i, y_i)$ をすべて通る $d$ 次以下の多項式は $f(x) = \sum_i y_i \prod_{j \ne i} \frac{x - x_j}{x_i - x_j}$ で、これを mod で計算します。 $x_i = i$ のときは、分子を左右からの累積積、分母を階乗で表せるので $O(d)$ になります。式の導き方は 多項式補間についての記事 に書いています。

mod は素数で、 $x$ は大きくても負でもかまいません。

答えが $n$ についての $d$ 次多項式になるとわかっているときに、 $n = 0, 1, \ldots, d$ の答えを愚直に求めておき、大きい $n$ での値を求めるのによく使います。

例( $1^k + 2^k + \cdots + n^k$ は $n$ の $k + 1$ 次多項式):

mod = 998244353
ys = [0]
for i in range(1, k + 2):
    ys.append((ys[-1] + pow(i, k, mod)) % mod)
print(lagrange_iota(ys, n, mod))

コード

def lagrange(xs, ys, x, mod):
    # 点 (xs[i], ys[i]) をすべて通る len(xs) - 1 次以下の多項式 f について f(x) を mod で求める
    # xs は mod で互いに異なり、mod は素数
    n = len(xs)
    res = 0
    for i in range(n):
        num = 1
        den = 1
        for j in range(n):
            if i == j:
                continue
            num = num * (x - xs[j]) % mod
            den = den * (xs[i] - xs[j]) % mod
        res += ys[i] * num * pow(den, mod - 2, mod)
    return res % mod


def lagrange_iota(ys, x, mod):
    # xs = 0, 1, ..., d のとき(ys[i] = f(i))。mod は d より大きい素数
    d = len(ys) - 1
    x %= mod
    if x <= d:
        return ys[x] % mod
    pre = [1] * (d + 2)  # pre[i] = (x - 0)(x - 1)...(x - (i - 1))
    for i in range(d + 1):
        pre[i + 1] = pre[i] * (x - i) % mod
    suf = [1] * (d + 2)  # suf[i] = (x - i)(x - (i + 1))...(x - d)
    for i in range(d, -1, -1):
        suf[i] = suf[i + 1] * (x - i) % mod
    fact = [1] * (d + 1)
    for i in range(1, d + 1):
        fact[i] = fact[i - 1] * i % mod
    inv_fact = [1] * (d + 1)
    inv_fact[d] = pow(fact[d], mod - 2, mod)
    for i in range(d, 0, -1):
        inv_fact[i - 1] = inv_fact[i] * i % mod
    res = 0
    for i in range(d + 1):
        t = ys[i] * pre[i] % mod * suf[i + 1] % mod * inv_fact[i] % mod * inv_fact[d - i] % mod
        res += -t if (d - i) & 1 else t
    return res % mod