Day 075-Q2 — FPS sqrt(Newton法 + NTT による形式的冪級数平方根)

2026-06-28 赤色 Master / Phase 8+ ★★★★★★★★★ FPS・Newton法・NTT・Tonelli-Shanks

問題

素数 $p = 998244353$ 上の形式的冪級数 $f(x) = \sum_{i=0}^{N-1} a_i x^i$ が与えられる。

$$g(x)^2 \equiv f(x) \pmod{x^N}$$

を満たす $g(x)$ を $\pmod{x^N}$ で求めよ。$a_0$ は $p$ 上で平方数であることが保証される。

制約

パラメータ範囲備考
$N$$1 \le N \le 2 \times 10^5$冪級数の次数
$a_i$$0 \le a_i < p$$p = 998244353$
$a_0$$p$ 上で平方数Tonelli-Shanks が適用可能

入出力例

入力例1

4
1 2 3 4

出力例1

1 1 1 3

$g = 1 + x + x^2 + 3x^3$ として $(1+x+x^2+3x^3)^2 \equiv 1+2x+3x^2+4x^3 \pmod{x^4}$ を確認できる。

概念図: Newton 反復による倍精度アップ

Newton 反復: $g^2 \equiv f$ の逐次精度向上 $g_1 = \sqrt{a_0}$ mod $x^1$ Tonelli-Shanks $g_2 = \frac{g_1 + f/g_1}{2}$ mod $x^2$ NTT + fps_inv $g_4 = \frac{g_2 + f/g_2}{2}$ mod $x^4$ 精度 2 倍 $g_N$ mod $x^N$ $O(N\log N)$ 精度: 1項 精度: 2項 精度: 4項 精度: N項 FPS 逆元の Newton 反復(内部で使用) $g^{-1}_{2k} \equiv 2g^{-1}_k - f \cdot (g^{-1}_k)^2 \pmod{x^{2k}}$ 初期値: $g^{-1}_1 = f_0^{-1} \bmod p$(モジュラー逆元) 各ステップ: NTT を 3 回使用 / 全体 $O(N \log N)$

ヒント

ヒント1(方向性)

$g^2 = f$ を $h(g) = g^2 - f = 0$ の零点問題として Newton 法で解く。更新式: $$g' \leftarrow g - \frac{g^2 - f}{2g} = \frac{g + f/g}{2}$$ 各ステップで精度が $k \to 2k$ 項に倍増し、$O(\log N)$ 回の NTT で $O(N \log N)$ 総計算量。

ヒント2(アプローチ)
  1. Tonelli-Shanks で $\sqrt{a_0} \bmod p$ を求める(初期値 $g_1$)
  2. FPS 逆元fps_inv)を実装: これも Newton 反復で $O(N \log N)$
  3. FPS sqrt 反復: g_new = (g + poly_mul(f, fps_inv(g))[:k]) * inv2
  4. 各ステップで k = min(2k, N) と打ち切る
ヒント3(ほぼ答え)
def fps_sqrt(f, n, MOD=998244353):
    inv2 = (MOD + 1) // 2
    g = [tonelli_shanks(f[0], MOD)]
    k = 1
    while k < n:
        k2 = min(2 * k, n)
        g_inv = fps_inv(g, k2, MOD)
        fg_inv = poly_mul(f[:k2], g_inv)[:k2]
        g = [(( g[i] if i < len(g) else 0) + fg_inv[i]) * inv2 % MOD
             for i in range(k2)]
        k = k2
    return g[:n]

模範解答

import sys
input = sys.stdin.readline
MOD = 998244353

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(3, (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 poly_mul(a, b):
    sz = len(a) + len(b) - 1; n = 1
    while n < sz: 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[:sz]

def fps_inv(f, n):
    g = [pow(f[0], MOD - 2, MOD)]; k = 1
    while k < n:
        k2 = min(2*k, n)
        fg = poly_mul(f[:k2], poly_mul(g, g))[:k2]
        g2 = [(2*g[i] if i < len(g) else 0) % MOD for i in range(k2)]
        g = [(g2[i] - fg[i]) % MOD for i in range(k2)]; k = k2
    return g[:n]

def tonelli_shanks(n, p):
    if n == 0: return 0
    if p % 4 == 3: return pow(n, (p+1)//4, p)
    q, s = p-1, 0
    while q % 2 == 0: q //= 2; s += 1
    z = 2
    while pow(z, (p-1)//2, p) != p-1: z += 1
    m, c, t, r = s, pow(z, q, p), pow(n, q, p), pow(n, (q+1)//2, p)
    while True:
        if t == 1: return r
        i, tmp = 1, t*t%p
        while tmp != 1: tmp = tmp*tmp%p; i += 1
        b = pow(c, 1 << (m-i-1), p)
        m, c, t, r = i, b*b%p, t*b*b%p, r*b%p

def fps_sqrt(f, n):
    inv2 = (MOD + 1) // 2
    g = [tonelli_shanks(f[0], MOD)]; k = 1
    while k < n:
        k2 = min(2*k, n)
        g_inv = fps_inv(g, k2)
        fg_inv = poly_mul(f[:k2], g_inv)[:k2]
        g = [((g[i] if i < len(g) else 0) + fg_inv[i]) * inv2 % MOD
             for i in range(k2)]
        k = k2
    return g[:n]

def solve():
    N = int(input())
    a = list(map(int, input().split()))
    print(*fps_sqrt(a, N))

solve()

Step-by-Step 解説

Step 1: Tonelli-Shanks による $\sqrt{a_0} \bmod p$

$p \equiv 3 \pmod{4}$ のとき $\sqrt{n} = n^{(p+1)/4} \bmod p$ で直接計算できる。$p = 998244353$ は $p \equiv 1 \pmod{4}$ なので Tonelli-Shanks の完全版が必要。$p-1 = 2^s \cdot q$ と分解し、非平方数 $z$ を見つけて反復で解を絞り込む。

Step 2: FPS 逆元の Newton 反復

$g^{-1}_{2k} \equiv 2g^{-1}_k - f \cdot (g^{-1}_k)^2 \pmod{x^{2k}}$。これは $h(g) = g^{-1} - f = 0$ の Newton 反復。各ステップで NTT を使い、$O(N \log N)$ 総計算量。

Step 3: FPS sqrt の Newton 反復

$g' = \frac{g + f/g}{2} = \frac{1}{2}\left(g + f \cdot g^{-1}\right)$。先に $g^{-1} \bmod x^{2k}$ を求め、$f \cdot g^{-1}$ を NTT で計算し、$(g + f \cdot g^{-1}) / 2$ が次の $g$ となる。

Step 4: 全体計算量

各ステップで NTT $O(k \log k)$。$k = 1, 2, 4, \ldots, N$ と倍増するので総和は $O(N \log N)$。

よくあるミス

ミス原因正しい書き方
inv2 の計算ミス2 の逆元が必要(MOD + 1) // 2 ($p$ が奇素数なら正確)
配列の打ち切り忘れk2 = min(2k, N) 忘れ常に [:k2] で切り詰める
a_0 = 0 のケース逆元が存在しないtonelli_shanks(0, p) = 0 を冒頭で処理

次のステップ

  • 発展問題: FPS exp($e^{f(x)} \bmod x^N$)を同様の Newton 法で実装する($g' \leftarrow g(1 - \log g + f)$)

自己評価

理解度:

自分の回答:

気づき・メモ: