Day 072-Q1 — 行列木定理拡張(重み付き全域木数え上げ・固有多項式)

2026-06-25 赤色 Master / Phase 8+ ★★★★★★★★★ 行列木定理・ラプラシアン・行列式

問題

$N$ 頂点 $M$ 辺の無向重み付きグラフ $G$ が与えられる。辺 $e_i$ は頂点 $u_i$, $v_i$ を結び、重み $w_i \ge 1$ を持つ。

重み付き全域木の重みを辺重みの積と定義する。すなわち全域木 $T$ の重みは $\prod_{e \in T} w_e$ である。

全域木の重みの総和 $\displaystyle W = \sum_{T: \text{spanning tree}} \prod_{e \in T} w_e$ を $10^9+7$ で割った余りを求めよ。

これは Kirchhoff の行列木定理の重み付き版により、重み付きラプラシアン行列 $L$ の任意の $(i,i)$ 余因子として計算できる。

制約

パラメータ範囲備考
$N$$2 \le N \le 500$頂点数
$M$$1 \le M \le \min(N(N-1)/2,\ 2\times10^4)$辺数
$w_i$$1 \le w_i \le 10^9$辺重み
グラフは連結

入出力例

入力例 1

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

出力例 1

252

入力例 2

3 3
1 2 1
2 3 1
1 3 1

出力例 2

3

概念図: 重み付き行列木定理

Kirchhoff の行列木定理(重み付き版) 重み付きラプラシアン L の任意 (i,i) 余因子 = 全域木の辺重み積の総和 グラフ例(N=4) 1 2 3 4 w=2 w=3 w=4 w=5 w=6 重み付きラプラシアン L L[i][i] = 接続する辺の重みの和 L[i][j] = -(辺(i,j)の重み) 1 2 3 4 1: [ 5 -2 -3 0] 2: [-2 11 -4 -5] 3: [-3 -4 13 -6] 4: [ 0 -5 -6 11] 0行0列を除いた 3×3 行列の行列式 det([[11,-4,-5],[-4,13,-6],[-5,-6,11]]) = 252 mod (10^9+7)

ヒント(段階的開示)

ヒント1: 方向性
Kirchhoff の行列木定理(Matrix-Tree Theorem)の重み付き版を使う。重み付きラプラシアン行列 $L$ を構築し、任意の $(N-1)\times(N-1)$ 余因子行列の行列式を mod $p$ で計算する。
ヒント2: アプローチ

重み付きラプラシアン行列の定義:

  • $L[i][i] = \sum_{j: (i,j) \in E} w_{ij}$
  • $L[i][j] = -w_{ij}$(辺 $(i,j)$ が存在する場合)

0行0列を除いた $(N-1)\times(N-1)$ 部分行列の行列式を求める。Gaussian Elimination(mod 素数)で $O(N^3)$ で計算できる。

ヒント3: コード骨格
def mat_det_mod(mat, mod):
    n = len(mat)
    mat = [row[:] for row in mat]
    det = 1
    for col in range(n):
        pivot = -1
        for row in range(col, n):
            if mat[row][col] != 0:
                pivot = row; break
        if pivot == -1: return 0
        if pivot != col:
            mat[col], mat[pivot] = mat[pivot], mat[col]
            det = (-det) % mod
        det = det * mat[col][col] % mod
        inv = pow(mat[col][col], mod-2, mod)
        for row in range(col+1, n):
            ratio = mat[row][col] * inv % mod
            for c in range(col, n):
                mat[row][c] = (mat[row][c] - ratio * mat[col][c]) % mod
    return det

模範解答 (Python)

import sys
input = sys.stdin.readline

def solve():
    MOD = 10**9 + 7
    N, M = map(int, input().split())

    L = [[0] * N for _ in range(N)]
    for _ in range(M):
        u, v, w = map(int, input().split())
        u -= 1; v -= 1
        w %= MOD
        L[u][u] = (L[u][u] + w) % MOD
        L[v][v] = (L[v][v] + w) % MOD
        L[u][v] = (L[u][v] - w) % MOD
        L[v][u] = (L[v][u] - w) % MOD

    sub = [[L[i][j] for j in range(1, N)] for i in range(1, N)]
    n = N - 1
    mat = [row[:] for row in sub]
    det = 1
    for col in range(n):
        pivot = -1
        for row in range(col, n):
            if mat[row][col] % MOD != 0:
                pivot = row; break
        if pivot == -1:
            print(0); return
        if pivot != col:
            mat[col], mat[pivot] = mat[pivot], mat[col]
            det = (-det) % MOD
        det = det * mat[col][col] % MOD
        inv = pow(mat[col][col], MOD - 2, MOD)
        for row in range(col + 1, n):
            if mat[row][col] == 0: continue
            ratio = mat[row][col] * inv % MOD
            for c in range(col, n):
                mat[row][c] = (mat[row][c] - ratio * mat[col][c]) % MOD

    print(det % MOD)

solve()

Step-by-Step 解説

Step 1: 行列木定理の重み付き版

通常の行列木定理では全域木の個数を数えるが、重み付き版では辺に重み $w_e$ を割り当てることで全域木の重み積の総和を計算できる。

重み付きラプラシアン:

$$L[i][j] = \begin{cases} \sum_{k: (i,k) \in E} w_{ik} & i = j \\ -w_{ij} & (i,j) \in E \\ 0 & \text{otherwise} \end{cases}$$

Step 2: 余因子行列式の計算

行列木定理により $W = \det(\tilde{L})$、ここで $\tilde{L}$ は $L$ から任意の1行1列を除いた行列。どの行列を選んでも値は同じ(行列木定理の基本性質)。

Step 3: mod p でのガウス消去

  1. 行スワップごとに符号を反転($\det \to -\det$)
  2. ピボット元で他行を消去(逆元を使用)
  3. 対角成分の積が行列式

時間計算量: $O(N^3)$

Step 4: 実装上の注意

  • Pythonの % はmodulo演算子で負数も正規化される
  • pow(x, MOD-2, MOD) でフェルマーの小定理による逆元計算
  • $N = 500$ のとき $O(N^3) = 1.25 \times 10^8$ 操作、Pythonではやや重いので必要なら PyPy 推奨

よくあるミス

ミス原因正しい書き方
行削除インデックス間違い0行0列以外を除いてしまうrange(1, N) で確実に
符号の扱い% MOD を行スワップ後の符号変更に忘れdet = (-det) % MOD
ゼロ除算ピボットが0の場合に逆元計算ピボット存在確認後に計算
負値の剰余Python は自動正規化されるが C++ は注意Python は % MOD で安全

次のステップ

  • 発展問題: 有向グラフの全域木数え上げ(BEST定理、オイラー路との関係)
  • 固有値との関係(ラプラシアン固有値の積 = $N^{N-2}$ for 完全グラフ)
  • 電気ネットワーク理論との接続(有効抵抗 = 行列木定理の比)

自己評価

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

自分の回答:

気づき・メモ: