Official
C - グループ対抗勝ち抜き戦 / Group Knockout Battle Editorial by admin
GPT 5.2 High概要
残っている生徒を出席番号順に \(K\) 人ずつのグループに分け、各グループの「実力最大(同点なら番号最小)」だけを残す操作を、1 人になるまで繰り返したときの優勝者(出席番号)を求めます。
考察
各ラウンドでやることは本質的に「現在残っている列を、左から \(K\) 個ずつ区切って、その区間の最大要素(同点は左=番号が小さい方)を1つ選ぶ」だけです。
ここで重要な観察は次の通りです。
- 各ラウンドは、残っている人数を \(M\) とすると \(O(M)\) で処理できる
(各生徒は自分の属するグループ内で一度だけ比較される) - ラウンドを重ねると人数はおおよそ \(M \to \lceil M/K \rceil\) と減るため、全体の処理量は
\(N + \left\lceil \frac{N}{K}\right\rceil + \left\lceil \frac{N}{K^2}\right\rceil + \cdots\) という等比級数になり、合計で \(O(N)\) 程度に収まります(特に最悪の \(K=2\) でも合計は \(<2N\))。
逆に、素朴に「グループ分けを毎回作り直す」「勝ち残り判定を重いデータ構造で管理する」などをしても正しくは解けますが、Pythonでは定数倍が大きくなりがちです。本解法はそのままシミュレーションするが、各ラウンドを線形に処理することで高速にします。
また、同点処理は「実力が同じなら出席番号が小さい方」なので、0-index の内部表現では
- 実力が大きい方が勝ち
- 実力が同じならインデックス(出席番号)が小さい方が勝ち
を比較条件に入れればOKです。
アルゴリズム
- 残っている生徒のリスト
remを、最初は[0, 1, 2, ..., N-1](0-index)で持つ。 remの長さが 1 になるまで以下を繰り返す。remを左から \(K\) 個ずつ見ていき、各ブロック(最後は \(K\) 未満でも可)について- そのブロック内で「\((A_i, -i)\) が最大」になる \(i\) を選ぶ
(実力最大、同点なら \(i\) が小さい=出席番号が小さい) - 選ばれた \(i\) を次ラウンド用リスト
newに追加する
- そのブロック内で「\((A_i, -i)\) が最大」になる \(i\) を選ぶ
rem = newとして次ラウンドへ
- 最後に残った 1 人
rem[0]を 1-index に戻して出力する。
例えば \(K=3\)、残りが [2,5,7, 1,4](出席番号順)なら
- グループ1:
[2,5,7]の勝者を選ぶ
- グループ2:
[1,4]の勝者を選ぶ
という処理を毎ラウンド行います。
計算量
- 時間計算量: \(O\!\left(N + \frac{N}{K} + \frac{N}{K^2} + \cdots\right) = O(N)\)
- 空間計算量: \(O(N)\)(残りの生徒番号リストを保持)
実装のポイント
同点処理:
a2 > bestA or (a2 == bestA and idx2 < best)のように、実力が同じなら番号が小さい方を採用します。高速入出力: \(N \le 5\times 10^5\) なので、
sys.stdin.buffer.read()を使った一括読み取り+手動パースで高速化しています。0-index / 1-index: 内部では 0-index(
0..N-1)で扱い、出力時に+1しています。ソースコード
import sys
data = sys.stdin.buffer.read()
n_data = len(data)
i = 0
# Read N
while i < n_data and data[i] <= 32:
i += 1
N = 0
while i < n_data and data[i] > 32:
N = N * 10 + (data[i] - 48)
i += 1
# Read K
while i < n_data and data[i] <= 32:
i += 1
K = 0
while i < n_data and data[i] > 32:
K = K * 10 + (data[i] - 48)
i += 1
A = [0] * N
for t in range(N):
while i < n_data and data[i] <= 32:
i += 1
num = 0
while i < n_data and data[i] > 32:
num = num * 10 + (data[i] - 48)
i += 1
A[t] = num
rem = list(range(N))
K_local = K
A_local = A
while len(rem) > 1:
m = len(rem)
new = []
new_append = new.append
rem_local = rem
i = 0
while i < m:
best = rem_local[i]
bestA = A_local[best]
end = i + K_local
if end > m:
end = m
j = i + 1
while j < end:
idx2 = rem_local[j]
a2 = A_local[idx2]
if a2 > bestA or (a2 == bestA and idx2 < best):
best = idx2
bestA = a2
j += 1
new_append(best)
i = end
rem = new
sys.stdout.write(str(rem[0] + 1) + "\n")
この解説は gpt-5.2-high によって生成されました。