Day 072-Q3 — 多項式合成・Taylor展開(FPS Composition via Newton法)

2026-06-25 赤色 Master / Phase 8+ ★★★★★★★★★ 形式的冪級数・Taylor Shift・NTT・多項式合成

問題

$\mathbb{Z}/p\mathbb{Z}$($p = 998244353$)上の形式的冪級数 $f(x)$ と $g(x)$ が与えられる(共に $x^N$ の係数まで)。

以下を処理せよ:

  1. 合成 $h(x) = f(g(x)) \bmod x^N$
  2. 平行移動 $f(x + c) \bmod x^N$(定数 $c$ だけシフト)

$g(0) = 0$ が保証される(合成が well-defined)。

制約

パラメータ範囲備考
$N$$1 \le N \le 2^{17}$係数の個数
$f_0$$f_0 \ne 0$逆合成の存在条件
係数$[0, p)$$p = 998244353$

入出力例

入力例 1

4
1 1 1 1
0 1 0 0
2

出力例 1

Composition f(g(x)): 1 1 1 1
Taylor shift f(x+2): 27 27 9 1

入力例 2

4
0 1 0 0
0 2 3 4
0

出力例 2

Composition f(g(x)): 0 2 3 4
Taylor shift f(x+0): 0 1 0 0

概念図: Taylor Shift の数学的構造

Taylor Shift: f(x+c) の畳み込みによる高速計算 f(x+c) = Σ_k (Σ_j f_j·C(j,k)·c^(j-k)) x^k → A と B の畳み込みに帰着 展開の流れ f(x) = f₀ + f₁x + f₂x² + ... f(x+c) = Σⱼ fⱼ (x+c)ʲ = Σⱼ fⱼ Σₖ C(j,k) c^(j-k) xᵏ = Σₖ [Σⱼ fⱼ j!/k!(j-k)! c^(j-k)] xᵏ [xᵏ] = (1/k!) Σⱼ (fⱼ·j!) · (c^(j-k)/(j-k)!) A[j] = f[j] · j! B[j] = c^j / j! 畳み込み A★B_rev の N-1+k 番目の係数 × (1/k!) → NTT で O(N log N) で計算可能 配列変換の可視化(N=4, c=2) f = [1, 1, 1, 1] A[j] = f[j]·j!: [1·0!, 1·1!, 1·2!, 1·3!] = [1, 1, 2, 6] B[j] = 2^j / j!: [1, 2, 2, 4/3] B_reversed: [4/3, 2, 2, 1] A ★ B_rev の [3], [4], [5], [6] 番目を使用 各 k で (1/k!) を掛けて答えを得る

ヒント(段階的開示)

ヒント1: 方向性
Taylor shift $f(x+c)$ は EGF 的な変換で実現できる。$f(x+c) = \sum_j f_j (x+c)^j$ を展開すると畳み込みに帰着する。
ヒント2: アプローチ

$A_j = f_j \cdot j!$、$B_j = c^j / j!$ とおくと:

$$[x^k] f(x+c) = \frac{1}{k!} \sum_{j \ge k} A_j \cdot B_{j-k}$$

これは $A$ と $B$ の畳み込みの特定の係数。NTT で $O(N \log N)$。

ヒント3: コード骨格
def taylor_shift(f, c, N, MOD):
    # 階乗前処理
    fact = [1] * N
    for i in range(1, N): fact[i] = fact[i-1] * i % MOD
    inv_fact = [pow(fact[N-1], MOD-2, MOD)] + [0]*(N-1)
    for i in range(N-2, -1, -1): inv_fact[i] = inv_fact[i+1]*(i+1)%MOD

    # A[j] = f[j]*j!
    a = [f[j] * fact[j] % MOD for j in range(N)]
    # B[j] = c^j/j!
    cp = [1]*N
    for i in range(1,N): cp[i] = cp[i-1]*c%MOD
    b = [cp[j]*inv_fact[j]%MOD for j in range(N)]

    # 畳み込み (A, B_reversed)
    product = ntt_conv(a, b[::-1], MOD)
    return [product[N-1+k]*inv_fact[k]%MOD for k in range(N)]

模範解答 (Python)

import sys
from sys import stdin

MOD = 998244353
g_prim = 3

def ntt(a, invert):
    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_prim, (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:
        inv_n = pow(n, MOD-2, MOD)
        for i in range(n): a[i] = a[i] * inv_n % MOD

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

def taylor_shift(f, c, N):
    fact = [1]*N
    for i in range(1, N): fact[i] = fact[i-1]*i%MOD
    inv_fact = [1]*N
    inv_fact[N-1] = pow(fact[N-1], MOD-2, MOD)
    for i in range(N-2, -1, -1): inv_fact[i] = inv_fact[i+1]*(i+1)%MOD

    a = [f[j]*fact[j]%MOD for j in range(N)]
    cp = [1]*N
    for i in range(1, N): cp[i] = cp[i-1]*c%MOD
    b = [cp[j]*inv_fact[j]%MOD for j in range(N)]
    b_rev = b[::-1]

    product = conv(a, b_rev)
    return [product[N-1+k]*inv_fact[k]%MOD for k in range(N)]

def solve():
    data = stdin.read().split()
    idx = 0
    N = int(data[idx]); idx += 1
    f = [int(data[idx+i]) for i in range(N)]; idx += N
    g = [int(data[idx+i]) for i in range(N)]; idx += N
    c = int(data[idx]); idx += 1

    # 合成 f(g(x)) - O(N^2) 直接版
    fg = [0]*N
    gpower = [0]*N
    gpower[0] = 1
    for k in range(N):
        for j in range(N):
            fg[j] = (fg[j] + f[k]*gpower[j]) % MOD
        if k+1 < N:
            new_gp = [0]*N
            for i in range(N):
                for j in range(N-i):
                    new_gp[i+j] = (new_gp[i+j]+gpower[i]*g[j])%MOD
            gpower = new_gp

    print("Composition f(g(x)):", *fg)

    # Taylor shift f(x+c)
    ts = taylor_shift(f, c%MOD, N)
    print(f"Taylor shift f(x+{c}):", *ts)

solve()

Step-by-Step 解説

Step 1: Taylor Shift の数学的背景

$$f(x+c) = \sum_j f_j (x+c)^j = \sum_j f_j \sum_k \binom{j}{k} c^{j-k} x^k = \sum_k \left[\sum_j f_j \binom{j}{k} c^{j-k}\right] x^k$$

Step 2: 畳み込み形式への変換

$[x^k] f(x+c) = \frac{1}{k!} \sum_j (f_j \cdot j!) \cdot \frac{c^{j-k}}{(j-k)!}$

$A_j = f_j \cdot j!$、$B_j = c^j / j!$ として畳み込みの係数に対応させる。

Step 3: NTT による高速化

畳み込みを NTT で $O(N \log N)$ で計算。$B$ を反転して $A \star B_{\rm rev}$ の $[N-1+k]$ 番目の係数が $k$ 次係数に対応。

Step 4: 多項式合成の計算量

任意 $g$ に対する $f(g(x))$ の一般的な高速アルゴリズムは困難。本問の直接法は $O(N^2)$。BSGS 法で $O(N^{1.5} \log N)$ まで落とせる。

よくあるミス

ミス原因正しい書き方
畳み込み位置ずれ$B$ 反転後の添え字対応product[N-1+k] が $k$ 次係数
階乗逆元の境界inv_fact[0] = 1 の確認$N=1$ でも動作確認
$c=0$ のとき$0^0 = 1$ の扱いcp[0] = 1 として初期化
NTT の長さ不足畳み込み結果の長さ$2N$ 以上の2のべき乗を確保

次のステップ

  • 発展問題: Bostan-Mori アルゴリズム(線形漸化式の $N$ 項目 $O(N \log^2 N)$)
  • 多項式合成の BSGS 法($O(N^{1.5} \log N)$)
  • 形式的冪級数の微分・積分との組み合わせ

自己評価

解いた後に記入してください

自分の回答:

気づき・メモ: