C - シード番号の割り当て / Seed Number Assignment 解説 by admin
Claude Opus 5 (High)概要
各参加者の合計スコア \(A_i + B_i\) が \(K\) 以上の人だけを抽出し、「合計スコアの降順、同点なら参加者番号の昇順」に並べたときの順位(シード番号)を求める問題です。典型的な「複合キーによるソート」の練習問題です。
考察
重要な気づき
前半戦・後半戦のスコアを個別に扱う必要はない
本戦進出の判定も、シード番号の優先順位も、すべて合計スコア \(S_i = A_i + B_i\) のみで決まります。したがって入力を読んだ時点で \(S_i\) を計算してしまえば、以降は「値 \(S_i\) と番号 \(i\) の組」だけを考えればよくなります。シード番号は「ソート後の並び順」そのもの
本戦進出者を優先順位どおりに並べたとき、先頭から \(1, 2, 3, \dots\) と番号を振るだけで答えになります。「順位を計算する」という難しい処理は不要で、単に並べてから前から番号を振ればよいのです。
注意点として、同点の参加者がいても「同じ順位」にはなりません(シード番号は重複なく割り当てられる)。同点なら参加者番号が小さい方が先に来るので、タイブレーク(同点時の順序決定)を必ず参加者番号の昇順で行う必要があります。ここを忘れると、同点ケースで WA になります。
素朴なアプローチの問題
「各参加者について、自分より優先順位が高い進出者が何人いるかを数える」という定義通りの実装をすると、参加者ごとに全員を走査するため \(O(N^2)\) になります。\(N \le 10^5\) なので \(10^{10}\) 回程度の比較となり、確実に TLE です。
これは、そもそも「全員の順位」を一度に求めたいのだから、1 回ソートすれば全員の順位が同時に決まることに気づけば解決します。ソートは \(O(N \log N)\) で済みます。
元の位置を保持する工夫
ソートしてしまうと参加者番号の情報が失われるため、出力ができなくなります。そこで - 「(合計スコア, 参加者番号)」のペアをソートする、または - 「参加者番号(インデックス)の列」をスコアをキーにしてソートする
のいずれかにして、元のインデックスを持ち歩くのがポイントです。答えは ans[i] のような長さ \(N\) の配列に書き戻して、最後に \(1\) 行ずつ出力します。
具体例
\(N = 5\), \(K = 100\) で
| \(i\) | \(A_i\) | \(B_i\) | \(S_i\) | 進出? |
|---|---|---|---|---|
| 1 | 50 | 60 | 110 | ○ |
| 2 | 30 | 40 | 70 | × |
| 3 | 100 | 20 | 120 | ○ |
| 4 | 60 | 50 | 110 | ○ |
| 5 | 0 | 0 | 0 | × |
進出者は \(\{1, 3, 4\}\)。優先順位順に並べると \(3 (120), 1 (110), 4 (110)\)(\(1\) と \(4\) は同点なので番号の小さい \(1\) が先)。よってシード番号は \(3 \to 1\), \(1 \to 2\), \(4 \to 3\) で、答えは順に 2, -1, 1, 3, -1 となります。
アルゴリズム
- 入力を読み、各 \(i\) について \(S_i = A_i + B_i\) を計算する。
- \(S_i \ge K\) を満たす参加者のインデックスだけを集めてリスト
candを作る。 candを キー \((-S_i,\ i)\) の昇順(= \(S_i\) の降順、同点なら \(i\) の昇順)でソートする。- 長さ \(N\) の答え配列を \(-1\) で初期化し、
candの先頭から順に \(1, 2, 3, \dots\) を代入する。 - 答え配列を先頭から出力する。
なお Python の sort は安定ソート(同じキーの要素の元の順序が保たれる)なので、cand を作った時点でインデックス昇順になっていることを利用して key=lambda i: -tot[i] だけでも正しく動きますが、意図を明示するために \((-S_i, i)\) をキーにするのが安全です。
計算量
- 時間計算量: \(O(N \log N)\)(ソートが支配的。入力の読み込みと \(S_i\) の計算は \(O(N)\))
- 空間計算量: \(O(N)\)
実装のポイント
オーバーフローの心配: \(A_i + B_i\) は最大 \(2 \times 10^9\) で 32bit 符号付き整数の範囲を超えます。C++ などでは
long longを使ってください(Python は多倍長整数なので問題ありません)。\(K\) も最大 \(2 \times 10^9\) です。降順ソートの書き方: Python では
keyにマイナスを付けてkey=lambda i: (-tot[i], i)とするのが簡単です。「スコアは降順、番号は昇順」と方向が異なるため、単にreverse=Trueを付けると番号の順序が逆になってしまい WA になります。ここは要注意です。入出力の高速化: \(N \le 10^5\) 行の入出力があるため、Python では
sys.stdin.buffer.read().split()で一括読み込み、出力は"\n".join(...)でまとめてsys.stdout.writeするのが安全です。input()とprint()の繰り返しは遅くなりがちです。境界条件: 判定は \(S_i \ge K\)(\(K\) 点「以上」)です。\(K = 0\) のときは全員進出、進出者が \(0\) 人のときは全行
-1になります。等号の扱いを間違えないようにしましょう。ソースコード
import sys
def main():
data = sys.stdin.buffer.read().split()
n = int(data[0]); k = int(data[1])
tot = [0] * n
idx = 2
for i in range(n):
tot[i] = int(data[idx]) + int(data[idx + 1])
idx += 2
cand = [i for i in range(n) if tot[i] >= k]
cand.sort(key=lambda i: (-tot[i], i))
ans = [-1] * n
r = 1
for i in cand:
ans[i] = r
r += 1
sys.stdout.write("\n".join(map(str, ans)))
main()
この解説は claude5opus-high によって生成されました。