Day 019-Q2 — 多項式多点評価・補間

2026-05-02 赤色 Master / Phase 8+ ★★★★★★★★★ Multipoint Eval / Interpolation

問題

素数 $p = 998244353$ の下で計算せよ。

パートA: $N-1$ 次多項式 $f(x)$ と $M$ 個の評価点で $f(x_i)$ を全て求めよ。

パートB: $N$ 個の点 $(x_i, y_i)$ を通る $N-1$ 次多項式の係数列を求めよ(ラグランジュ補間)。

制約

$1 \le N, M \le 10^5$
$0 \le c_i, x_i, y_i < p$

入出力例

入力例 1 (パートA)

3 3
1 2 1
0 1 2

出力例 1

1 4 9

ヒント (段階的開示)

ヒント1: 方向性
素朴に各点でホーナー法は O(NM) で TLE。分割統治 + FFT/NTT を使って $O(N \log^2 N)$ に落とす。
ヒント2: アプローチ
多点評価:subproduct tree を構築し、根の多項式 $f$ から $f \mod \text{左の子}$、$f \mod \text{右の子}$ を再帰計算。補間は $f(x) = \sum y_i \cdot \frac{P(x)}{(x-x_i) P'(x_i)}$ を分割統治で。
ヒント3: 誘導
def build_subproduct_tree(xs):
    # 各葉は (x - xs[i])、内部ノードは左右の積
def multieval(f, tree, size, n):
    # f mod tree[node] を再帰で
    # 葉では定数項 = f(xs[i])

模範解答 (Python)

import sys
input = sys.stdin.readline

MOD = 998244353
g = 3

def ntt(a, invert=False):
    n = len(a)
    j = 0
    for i in range(1, n):
        bit = n >> 1
        while j & bit:
            j ^= bit; bit >>= 1
        j ^= bit
        if i < j:
            a[i], a[j] = a[j], a[i]
    length = 2
    while length <= n:
        w = pow(g, (MOD - 1) // length, MOD)
        if invert: w = pow(w, MOD - 2, MOD)
        for i in range(0, n, length):
            wn = 1
            for k in range(length // 2):
                u = a[i + k]
                v = a[i + k + length // 2] * wn % MOD
                a[i + k] = (u + v) % MOD
                a[i + k + length // 2] = (u - v) % MOD
                wn = wn * w % MOD
        length <<= 1
    if invert:
        n_inv = pow(n, MOD - 2, MOD)
        for i in range(n): a[i] = a[i] * n_inv % MOD

def poly_mul(a, b):
    result_len = len(a) + len(b) - 1
    n = 1
    while n < result_len: n <<= 1
    fa = a + [0] * (n - len(a))
    fb = b + [0] * (n - len(b))
    ntt(fa); ntt(fb)
    for i in range(n): fa[i] = fa[i] * fb[i] % MOD
    ntt(fa, True)
    return fa[:result_len]

def poly_inv(a, n):
    res = [pow(a[0], MOD - 2, MOD)]
    length = 1
    while length < n:
        length <<= 1
        tmp = a[:min(len(a), length)]
        tmp_r = poly_mul(poly_mul(res, res), tmp)
        new_res = [0] * length
        for i in range(min(length, len(res))):
            new_res[i] = (2 * res[i] - tmp_r[i]) % MOD
        for i in range(len(res), length):
            new_res[i] = (-tmp_r[i]) % MOD
        res = new_res
    return res[:n]

def poly_divmod(a, b):
    if len(a) < len(b): return [0], a
    n = len(a) - len(b) + 1
    a_rev = a[::-1][:n]
    b_rev = b[::-1][:n]
    b_inv = poly_inv(b_rev, n)
    q = poly_mul(a_rev, b_inv)[:n][::-1]
    qb = poly_mul(q, b)
    r = [(a[i] - (qb[i] if i < len(qb) else 0)) % MOD for i in range(len(b) - 1)]
    return q, r

def poly_mod(f, m):
    _, r = poly_divmod(f, m)
    return r

def build_subproduct_tree(xs):
    n = len(xs)
    size = 1
    while size < n: size <<= 1
    tree = [[0]] * (2 * size)
    for i in range(n):
        tree[size + i] = [(-xs[i]) % MOD, 1]
    for i in range(n, size):
        tree[size + i] = [1]
    for i in range(size - 1, 0, -1):
        tree[i] = poly_mul(tree[2*i], tree[2*i+1])
    return tree, size

def multieval(f, tree, size, n):
    tree_f = [None] * (2 * size)
    tree_f[1] = poly_mod(f, tree[1])
    for i in range(1, size):
        tree_f[2*i] = poly_mod(tree_f[i], tree[2*i])
        tree_f[2*i+1] = poly_mod(tree_f[i], tree[2*i+1])
    return [tree_f[size + i][0] % MOD if tree_f[size + i] else 0 for i in range(n)]

def main():
    N, M = map(int, input().split())
    c = list(map(int, input().split()))
    xs = list(map(int, input().split()))
    tree, size = build_subproduct_tree(xs)
    vals = multieval(c, tree, size, M)
    print(*vals)

main()

Step-by-Step 解説

1部分積木の構築
評価点 $x_0, \ldots, x_{n-1}$ から subproduct tree を作る。各葉は $(x - x_i)$、内部ノードは左右の積。
2多点評価(分割統治)
根の $f$ から始め、各ノードで $f \mod \text{子}$ を計算。葉で $f(x_i)$ = 定数項。
3ラグランジュ補間
$f(x) = \sum y_i \cdot \frac{P(x)}{(x-x_i) P'(x_i)}$。$P'(x_i)$ を多点評価、$P(x)/(x-x_i)$ を分割統治で。
4計算量
部分積木構築、多点評価、補間ともに $O(N \log^2 N)$。

よくあるミス

ミス原因正しい書き方
poly_inv で a[0]=0逆元が存在しない事前チェックが必要
subproduct tree の空葉処理[1] (単位元) で埋めるサイズを2のべき乗に揃える
ラグランジュで xi が重複P'(xi)=0 になる問題制約で禁止されているが要確認

次のステップ

  • 発展: 任意の素数 mod や多倍長整数での多項式演算、GCD 多項式

自己評価

自分の回答

気づき・メモ