B - 成績表の更新 / Updating the Report Card Editorial by admin
Gemini 3.0 Flash概要
\(N\) 人の生徒の初期点数に対し、\(M\) 回の更新(点数の書き換え)を行った後、最終的な点数が \(K\) 点未満である生徒の総数を求める問題です。
考察
この問題で重要なのは、「ある生徒に対して複数回の更新が行われた場合、最後に実行された更新だけが有効になる」という点です。
効率的な処理の必要性
生徒数 \(N\) と更新回数 \(M\) は共に最大 \(10^5\) です。 - 愚直に「更新のたびに全生徒の点数を確認して補習対象者を数える」という処理をすると、計算量は \(O(N \times M)\) となり、最大で \(10^{10}\) 回程度の計算が必要になります。これは一般的な制限時間(2秒)には間に合いません。 - そこで、「すべての更新を反映させた後の最終的な状態」を一度だけ作り、最後に \(1\) 回だけ集計を行うアプローチをとります。これなら \(O(N + M)\) の計算量で済み、十分に間に合います。
更新の管理
提示されたコードでは、更新情報を管理するために辞書(ハッシュマップ)を利用しています。辞書に 生徒番号: 点数 という形式で保存することで、同じ生徒番号に対して新しい点数が来るたびに値が上書きされ、自然と「最後の更新内容」だけを残すことができます。
アルゴリズム
以下の手順で解いています。
- 初期データの読み込み: 生徒数 \(N\)、更新回数 \(M\)、基準点 \(K\)、および各生徒の初期点数を取得します。
- 更新情報の集約:
\(M\) 個の更新クエリを順に読み込み、辞書
final_updatesに格納します。これにより、各生徒の最終的な更新後の点数が確定します。 - 補習対象者の集計:
生徒を「更新があった生徒」と「更新がなかった生徒」の2グループに分けてカウントします。
- 更新があった生徒: 辞書
final_updatesの値を調べ、\(K\) 点未満の人数を数えます。 - 更新がなかった生徒: 初期点数のリストを調べ、その生徒の番号が辞書に含まれていない(=更新されていない)かつ初期点数が \(K\) 点未満の人数を数えます。
- 更新があった生徒: 辞書
- 結果の出力: 2つのグループの合計人数を出力します。
計算量
- 時間計算量: \(O(N + M)\)
- 入力の読み込みに \(O(N + M)\)、更新情報の辞書への登録に \(O(M)\)、最終的な集計(全生徒の走査)に \(O(N)\) かかります。辞書や集合(set)の操作は平均 \(O(1)\) であるため、全体として線形時間で処理可能です。
- 空間計算量: \(O(N + M)\)
- 初期点数のリスト(\(N\)要素)と、最悪の場合すべての生徒が更新される際の辞書(\(M\)要素)を保持するためのメモリが必要です。
実装のポイント
高速な入力:
sys.stdin.read().split()を使用することで、大量の入力データを一括で読み込み、高速に処理しています。データの区別:
updated_ids = set(final_updates.keys())を作成することで、「その生徒が更新されたかどうか」を \(O(1)\) で判定できるようにしています。1-indexed への対応: 問題文では生徒番号が \(1\) から \(N\) で与えられるため、
enumerate(initial_scores, 1)を使ってループのインデックスを \(1\) 始まりに調整しています。ソースコード
import sys
def main():
# 入力データを一括で読み込み、イテレータとして処理します
# これにより、大量の入力データに対しても効率的にアクセスできます
input_data = sys.stdin.read().split()
if not input_data:
return
it = iter(input_data)
# 基本設定の読み込み
n = int(next(it))
m = int(next(it))
k = int(next(it))
# 各生徒の初期点数をリストとして保持します
initial_scores = [int(next(it)) for _ in range(n)]
# 更新情報を辞書形式で管理します。
# キーを生徒番号、値を点数とすることで、同一生徒への複数回の更新があっても
# 自然に「最後の更新内容」のみを保持することができます。
final_updates = {}
for _ in range(m):
p = int(next(it))
v = int(next(it))
final_updates[p] = v
# 更新が行われた生徒のIDを集合(set)として抽出します(O(1)での存在確認のため)
updated_ids = set(final_updates.keys())
# 最終的な補習対象者の集計
# アプローチ:
# 1. 更新があった生徒については、辞書に格納された「最終的な点数」を確認する
# 2. 更新がなかった生徒については、初期点数リストの値をそのまま確認する
# 更新があった生徒のカウント
count_from_updates = sum(1 for score in final_updates.values() if score < k)
# 更新がなかった生徒のカウント
# enumerate(..., 1) を用いて1-indexedの生徒番号を取得します
count_from_initial = sum(1 for i, score in enumerate(initial_scores, 1)
if i not in updated_ids and score < k)
# 合計人数を出力
sys.stdout.write(str(count_from_updates + count_from_initial) + '\n')
if __name__ == '__main__':
main()
この解説は gemini-3-flash-preview によって生成されました。
posted:
last update: