Day 043-Q5 — 行列ランク計算 mod p(Gauss消去 + 更新クエリ)

2026-05-27 赤色 Master / Phase 8+ ★★★★★★★★★ Gaussian Elimination over F_p + Online Update

問題

$N \times M$ 行列 $A$(各要素は $\mathbb{F}_p$, $p = 998244353$)が与えられる。$Q$ 個のクエリを処理せよ。

制約

$1 \le N, M \le 300$
$1 \le Q \le 10^4$
$0 \le A[i][j] < p$
時間制限: 3sec / メモリ: 256MB

クエリ種別

種別形式意味
更新1 i j v$A[i][j] \leftarrow v$ に変更
クエリ2現在の行列 $A$ のランクを答える

入出力例

入力例 1

3 3 2
1 2 3
4 5 6
7 8 9
2
1 1 3 0
2

出力例 1

2
3

初期行列はランク2(行3=行1+行2の定数倍)。A[1][3]=0 にすると行1列が変わりランク3に。

概念図: Gauss 消去法の手順

Gauss 消去法: F_p 上の行階段形変換 元の行列 A 1 2 3 4 5 6 7 8 9 ← 行3 = 行1+行2 の線形結合 行階段形 1 0 -1 0 1 2 0 0 0 ← ゼロ行 → rank = 2 ピボット選択 各列でゼロでない行を選択 → その行を rank 行目に移動 → 他の行からその列を消去 rank = ピボットを立てた列数 F_p: 逆元 = pow(x, p-2, p) F_p 上の Gauss 消去の要点 実数の場合と同じ手順。ただし「割り算」= 逆元の乗算。 inv = pow(pivot, MOD - 2, MOD) # フェルマーの小定理: a^{p-1} = 1 mod p より a^{-1} = a^{p-2} 更新クエリの処理方針 毎回 Gauss 消去を再実行。N=M=300 では O(300³) ≈ 2.7×10^7。Q=10^4 クエリ2のたびに実行 → PyPy推奨。

ヒント(段階的開示)

ヒント1: 方向性
行列ランクは Gauss 消去法(前進消去)で $O(N^2 M)$ で計算できる。 クエリ型では毎回 Gauss 消去を行う単純実装で $O(Q \cdot N^2 M)$。$N,M \le 300$, クエリ2の回数が少なければ通る。
ヒント2: アプローチ
  1. 行列のコピーを作成(元の行列を破壊しない)
  2. 列を左から順にピボット選択
  3. ピボット行を選んで rank 行目に swap
  4. そのピボット列を他のすべての行から消去
  5. rank = ピボットを立てた列数
ヒント3: 実装骨格
MOD = 998244353

def gauss_rank(mat, N, M):
    A = [row[:] for row in mat]
    rank = 0
    for col in range(M):
        pivot = -1
        for row in range(rank, N):
            if A[row][col] != 0:
                pivot = row; break
        if pivot == -1: continue
        A[rank], A[pivot] = A[pivot], A[rank]
        inv = pow(A[rank][col], MOD - 2, MOD)
        for j in range(col, M):
            A[rank][j] = A[rank][j] * inv % MOD
        for row in range(N):
            if row != rank and A[row][col] != 0:
                f = A[row][col]
                for j in range(col, M):
                    A[row][j] = (A[row][j] - A[rank][j] * f) % MOD
        rank += 1
    return rank

模範解答 (Python)

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

def solve():
    N, M, Q = map(int, input().split())
    mat = []
    for _ in range(N):
        mat.append(list(map(int, input().split())))

    def gauss_rank():
        A = [row[:] for row in mat]
        rank = 0
        for col in range(M):
            pivot = -1
            for row in range(rank, N):
                if A[row][col] != 0:
                    pivot = row
                    break
            if pivot == -1:
                continue
            A[rank], A[pivot] = A[pivot], A[rank]
            inv = pow(A[rank][col], MOD - 2, MOD)
            for j in range(col, M):
                A[rank][j] = A[rank][j] * inv % MOD
            for row in range(N):
                if row != rank and A[row][col] != 0:
                    factor = A[row][col]
                    for j in range(col, M):
                        A[row][j] = (A[row][j] - A[rank][j] * factor) % MOD
            rank += 1
        return rank

    out = []
    for _ in range(Q):
        line = input().split()
        if line[0] == '1':
            i, j, v = int(line[1])-1, int(line[2])-1, int(line[3])
            mat[i][j] = v % MOD
        else:
            out.append(gauss_rank())

    print('\n'.join(map(str, out)))

solve()

Step-by-Step 解説

1Gauss 消去法の基本
列を左から順にピボットを選ぶ。ピボット行を選び、その列の他の全行を消去(行基本変形)。ランク = ピボットを立てた列の数。
2mod p の逆元
$\mathbb{F}_p$ 上では割り算 = 逆元の乗算。pow(x, MOD-2, MOD) でフェルマーの小定理による逆元計算($O(\log p)$)。実数と違い数値誤差がないため、任意の非零要素をピボットにできる。
3数値的安定性
実数 Gauss 消去では部分ピボット選択(絶対値最大の行を選択)が数値安定性に必要。mod p では任意の非零要素をピボットにできるため、単純な「最初に見つかった非零行」で十分。
4差分更新の工夫
行追加時のランク変化: 新しい行が既存の線形結合なら +0、独立なら +1。既約行階段形に変換済みの行列に 1 行追加するとき $O(N)$ で判定可能(行を消去して非零チェック)。

計算量

Gauss 消去 1回: $O(N^2 M)$($N \le M$ の場合 $O(N^2 M)$)
全体: $O(Q_2 \cdot N^2 M)$($Q_2$ = クエリ2の回数)
$N=M=300, Q_2=10^4$: 最悪 $3 \times 10^{11}$ → クエリ2の頻度に依存
空間: $O(NM)$

よくあるミス

ミス原因正しい書き方
inv = 1 / A[rank][col]float 誤差・mod p 不正pow(A[rank][col], MOD-2, MOD)
range(M) で全列処理左の列は既に0なので不要range(col, M) で効率化
行列のコピーを忘れてオリジナルを破壊A = mat はシャロウコピーA = [row[:] for row in mat]
rank の初期化忘れループ外で rank=0 を宣言rank = 0 を消去前に設定

次のステップ

  • 発展問題: オンライン行列ランク維持(行追加/削除をストリームで処理)
  • 類題: Library Checker "Determinant of Matrix"、CF 280E
  • 応用: 連立方程式の解の存在判定・線形空間の次元計算

自己評価