Day 057-Q2 — FFT × 多項式行列累乗(Cayley-Hamilton定理)

2026-06-10 赤色 Master / Phase 8+ ★★★★★★★★★ 多項式行列累乗 / FPS / グラフ数え上げ

問題

$N$ 頂点の有向グラフ $G$(隣接行列 $\mathbf{A}$、辺重み全て 1)が与えられる。頂点 $s$ から頂点 $t$ へ ちょうど $K$ 辺を使って到達するパス数を $\bmod{998244353}$ で求めよ。

$K \le 10^{18}$ のため通常の行列累乗は遅い。Cayley-Hamilton 定理を利用して $O(N^3 + N^2 \log K)$ で解け。

制約

パラメータ範囲
$N$$2 \le N \le 50$
$M$$0 \le M \le N^2$
$K$$1 \le K \le 10^{18}$
$s, t$$1 \le s, t \le N$(1-indexed)

入出力例

入力例 1

3 4 5 1 3
1 2
2 3
3 1
1 3

出力例 1

5

頂点 1→3 への長さ 5 のパスを数える。グラフは三角形 + 辺 1→3。

概念図: Cayley-Hamilton 定理の活用

Cayley-Hamilton: A^K を A^0,...,A^{N-1} の線形結合で表す 1. 特性多項式 p(λ) = det(λI - A) p(λ) = λ^N + c_{N-1}λ^{N-1} + ... + c_0 Cayley-Hamilton: p(A) = O(零行列) 2. λ^K mod p(λ) を多項式繰り返し二乗法で計算 λ^K ≡ r(λ) = r_0 + r_1λ + ... + r_{N-1}λ^{N-1} O(N^2 log K)(愚直)or O(N log N log K)(FFT) 3. A^K = r_0·I + r_1·A + ... + r_{N-1}·A^{N-1} ans = Σ r_i · [A^i]_{s,t} 多項式 mod 演算のイメージ λ^K(次数 K) 繰り返し二乗法: base = λ result = 1 while K: if K&1: result = result*base mod p base = base*base mod p K >>= 1 各ステップで次数を N-1 以下に保つ → O(N^2 log K) time

ヒント(段階的開示)

ヒント1: 方向性
Cayley-Hamilton 定理: $N \times N$ 行列 $\mathbf{A}$ はその特性多項式 $p(\lambda) = \det(\lambda I - \mathbf{A})$ を満たす($p(\mathbf{A}) = \mathbf{0}$)。 これにより $\mathbf{A}^K$ を $\mathbf{I}, \mathbf{A}, \ldots, \mathbf{A}^{N-1}$ の線形結合で表せる。 係数 $r_0, \ldots, r_{N-1}$ は $\lambda^K \bmod p(\lambda)$ の係数として計算できる。
ヒント2: アプローチ
  1. 特性多項式 $p(\lambda)$ を Leverrier-Fadeev 法で $O(N^3)$ に計算
  2. $\lambda^K \bmod p(\lambda)$ を多項式の繰り返し二乗法で計算: $O(N^2 \log K)$
  3. $r = [r_0, \ldots, r_{N-1}]$ を取得
  4. $\mathbf{A}^0, \ldots, \mathbf{A}^{N-1}$ を計算($O(N^4)$)
  5. $\text{ans} = \sum r_i \cdot [\mathbf{A}^i]_{s,t} \pmod{MOD}$
ヒント3: コード骨格
def poly_mul_mod_p(f, g, p, mod):
    """f*g mod p(多項式)"""
    h = [0] * (len(f) + len(g) - 1)
    for i, fi in enumerate(f):
        for j, gj in enumerate(g):
            h[i+j] = (h[i+j] + fi * gj) % mod
    return poly_mod_op(h, p, mod)

def poly_pow_mod_p(base, exp, p, mod):
    result = [1]  # 定数多項式 1
    while exp:
        if exp & 1:
            result = poly_mul_mod_p(result, base, p, mod)
        base = poly_mul_mod_p(base, base, p, mod)
        exp >>= 1
    return result

# lambda の多項式表現 = [0, 1]
lam = [0, 1]
r = poly_pow_mod_p(lam, K, char_poly, MOD)

模範解答 (Python)

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

def mat_mul(A, B, mod=MOD):
    n = len(A)
    C = [[0]*n for _ in range(n)]
    for i in range(n):
        for k in range(n):
            if A[i][k] == 0: continue
            for j in range(n):
                C[i][j] = (C[i][j] + A[i][k] * B[k][j]) % mod
    return C

def char_poly(A, mod=MOD):
    """Leverrier-Fadeev法で特性多項式 det(xI-A) の係数を計算"""
    n = len(A)
    p = [0] * (n + 1); p[n] = 1
    M = [[1 if i==j else 0 for j in range(n)] for i in range(n)]
    for k in range(1, n + 1):
        AM = mat_mul(A, M, mod)
        tr = sum(AM[i][i] for i in range(n)) % mod
        ck = (-tr * pow(k, mod-2, mod)) % mod
        p[n - k] = ck
        for i in range(n):
            AM[i][i] = (AM[i][i] + ck) % mod
        M = AM
    return p

def poly_mod_op(f, g, mod=MOD):
    f = list(f)
    dg = len(g) - 1
    g_inv = pow(g[-1], mod-2, mod)
    while len(f) > dg:
        if f[-1] == 0:
            f.pop(); continue
        c = f[-1] * g_inv % mod
        dd = len(f) - 1 - dg
        for i in range(len(g)):
            f[dd + i] = (f[dd + i] - c * g[i]) % mod
        while f and f[-1] == 0:
            f.pop()
    return f

def poly_mul(f, g, mod=MOD):
    if not f or not g: return []
    h = [0] * (len(f) + len(g) - 1)
    for i, fi in enumerate(f):
        if fi == 0: continue
        for j, gj in enumerate(g):
            h[i+j] = (h[i+j] + fi * gj) % mod
    return h

def poly_mul_mod_p(f, g, p, mod=MOD):
    return poly_mod_op(poly_mul(f, g, mod), p, mod)

def poly_pow_mod_p(base, exp, p, mod=MOD):
    result = [1]
    base = poly_mod_op(base[:], p, mod)
    while exp:
        if exp & 1:
            result = poly_mul_mod_p(result, base, p, mod)
        base = poly_mul_mod_p(base, base, p, mod)
        exp >>= 1
    return result

def solve():
    line = input().split()
    N, M, K = int(line[0]), int(line[1]), int(line[2])
    s, t = int(line[3])-1, int(line[4])-1

    A = [[0]*N for _ in range(N)]
    for _ in range(M):
        u, v = map(int, input().split())
        A[u-1][v-1] = 1

    p = char_poly(A)
    lam = [0, 1]
    r = poly_pow_mod_p(lam, K, p)
    while len(r) < N:
        r.append(0)

    powers = []
    cur = [[1 if i==j else 0 for j in range(N)] for i in range(N)]
    for _ in range(N):
        powers.append(cur)
        cur = mat_mul(cur, A)

    ans = 0
    for i in range(N):
        ans = (ans + r[i] * powers[i][s][t]) % MOD
    print(ans)

solve()

Step-by-Step 解説

Step 1: Cayley-Hamilton 定理

$N \times N$ 行列 $\mathbf{A}$ は自身の特性多項式 $p(\lambda) = \det(\lambda I - \mathbf{A})$ を満たす($p(\mathbf{A}) = \mathbf{0}$)。よって $\mathbf{A}^K$ は $\mathbf{I}, \mathbf{A}, \ldots, \mathbf{A}^{N-1}$ の線形結合で表せる。

Step 2: Leverrier-Fadeev 法

$O(N^3)$ で特性多項式の全係数を計算する。各ステップで $c_k = -\mathrm{tr}(AM_{k-1})/k$ を使う。

Step 3: 多項式繰り返し二乗法

$\lambda$ を「多項式 $[0, 1]$」として繰り返し二乗法。各ステップで $p(\lambda)$ で割った余りを取る。$O(N^2 \log K)$。

Step 4: 線形結合

$r = [r_0, \ldots, r_{N-1}]$ を用いて $\mathrm{ans} = \sum r_i [\mathbf{A}^i]_{s,t}$ を計算。

計算量

処理計算量
特性多項式(Leverrier-Fadeev)$O(N^3)$
$\lambda^K \bmod p(\lambda)$$O(N^2 \log K)$
$\mathbf{A}^0, \ldots, \mathbf{A}^{N-1}$ の計算$O(N^4)$
全体$O(N^4 + N^2 \log K)$

よくあるミス

ミス原因正しい書き方
$c_k$ の mod 逆元$c_k = -\mathrm{tr}/k$ の割り算pow(k, mod-2, mod) で逆元計算
特性多項式の符号det(A-λI) vs det(λI-A)最高次係数が $+1$ になることを確認
poly_mod の終了条件ゼロ多項式の扱い先頭ゼロを除去してから終了判定

次のステップ

  • 発展: Berlekamp-Massey + Kitamasa 法との統一視点(線形漸化式の N 項目高速計算)
  • 類題: AtCoder AGC005E, Codeforces 1528F
  • 応用: 隣接行列の $K$ 乗を使った確率遷移(Markov chain)

自己評価

自分の回答:

気づき・メモ: