D - コンテストの準備 / Contest Preparation 解説 by admin 印刷ページ
gpt-6-astra-medium概要
問題を難易度の低い順に調べながら、それまでに見た問題のうち配点が大きい \(K\) 問を管理します。その配点の合計が初めて \(P\) 以上になったときの難易度が答えです。
考察
難易度の上限を決めると、配点が大きい問題を選べばよい
選ぶ問題の難易度をすべて \(x\) 以下に制限したとします。
このとき、難易度が \(x\) 以下の問題から配点が大きい順に \(K\) 問を選べば、配点の合計を最大にできます。したがって、条件を満たす選び方が存在するのは、次の両方を満たす場合です。
- 難易度が \(x\) 以下の問題が \(K\) 問以上ある。
- その中で配点が大きい \(K\) 問の合計が \(P\) 以上である。
難易度の上限を大きくするほど選べる問題が増えるので、一度条件を満たせば、それ以降も条件を満たします。
そこで、問題を難易度の昇順に並べ、使える問題を \(1\) 問ずつ増やしていきます。
上位 \(K\) 個を効率よく管理する
すべての \(K\) 問の組合せを調べる方法は、組合せの数が膨大になるため間に合いません。また、問題を追加するたびに配点を並べ直しても、全体で \(O(N^2 \log N)\) 程度かかってしまいます。
必要なのは、これまでに見た問題の配点のうち、上位 \(K\) 個だけです。これらを最小ヒープで管理します。最小ヒープとは、格納した値の最小値をすぐに取得できるデータ構造です。
現在の上位 \(K\) 個の最小値を \(m\)、新しく見た問題の配点を \(a\) とすると、更新は次のようになります。
- \(a \leq m\) なら、上位 \(K\) 個は変更しない。
- \(a > m\) なら、\(m\) を取り除いて \(a\) を入れる。
配点の合計も保持すれば、置き換え時に \(a-m\) を加えるだけで更新できます。
例えば \(K=3\) で、保持している配点が \(4,7,10\) のとき、配点 \(8\) の問題が追加されたら \(4\) を \(8\) に置き換えます。合計は \(21\) から \(25\) になります。
アルゴリズム
- 各問題を
(難易度, 配点)の組として保存し、難易度の昇順にソートする。 - 先頭の \(K\) 問の配点で最小ヒープを作り、その合計を
totalとする。 totalが \(P\) 以上なら、先頭から \(K\) 番目の問題の難易度を出力して終了する。- 残りの問題を順に調べる。現在の問題の難易度を \(b\)、配点を \(a\) とする。
- \(a\) がヒープの最小値以下なら、何もしない。
- \(a\) がヒープの最小値より大きければ、最小値を \(a\) に置き換え、
totalを更新する。 - 更新後の
totalが \(P\) 以上なら、\(b\) を出力して終了する。
- 最後まで条件を満たさなければ、\(-1\) を出力する。
正しさの説明
最初は、見た問題がちょうど \(K\) 問なので、ヒープにはその中の配点上位 \(K\) 個が入っています。
新しい問題を追加したときも、その配点がヒープの最小値より大きい場合だけ置き換えることで、ヒープには常に、これまでに見た問題の配点上位 \(K\) 個が入るという性質が保たれます。よって、total はそれまでに見た問題から \(K\) 問を選ぶ場合の最大合計です。
初めて total が \(P\) 以上になったとき、現在の難易度を \(b\) とします。ここまでに見た問題の難易度はすべて \(b\) 以下なので、難易度の最大値を \(b\) 以下にして条件を満たせます。
一方、難易度が \(b\) 未満の問題はすでにすべて調べています。それらだけでは、問題数が足りないか、配点上位 \(K\) 個を選んでも合計が \(P\) 未満でした。したがって、\(b\) より小さい値を答えにすることはできず、\(b\) が最小です。
最後まで条件を満たさなければ、全問題の配点上位 \(K\) 個を選んでも合計が足りないため、答えは \(-1\) です。
計算量
- 時間計算量: \(O(N \log N)\)
ソートに \(O(N \log N)\)、ヒープの更新全体に \(O(N \log K)\) かかります。 - 空間計算量: \(O(N)\)
問題一覧に \(O(N)\)、ヒープに \(O(K)\) の領域を使います。
実装のポイント
Python の
heapqは最小ヒープを扱います。最小値はheap[0]で取得できます。heapq.heapreplace(heap, a)は、最小値を取り除いて \(a\) を挿入し、取り除いた値を返します。コードではこれを使って、合計をtotal += a - 取り除いた値と更新しています。置き換えが起きなければ合計は変わらないため、条件判定は初期化直後と置き換え直後だけで十分です。
同じ難易度の問題はまとめずに \(1\) 問ずつ処理して構いません。途中で条件を満たしても、出力する難易度は同じです。
配点の合計は最大 \(10^{14}\) になるため、固定長整数を使う言語では \(64\) ビット整数が必要です。Python の整数ではそのまま扱えます。
ソースコード
import sys
import heapq
def main():
input = sys.stdin.buffer.readline
N, K, P = map(int, input().split())
problems = []
for _ in range(N):
a, b = map(int, input().split())
problems.append((b, a))
problems.sort()
heap = [problems[i][1] for i in range(K)]
total = sum(heap)
heapq.heapify(heap)
if total >= P:
print(problems[K - 1][0])
return
replace = heapq.heapreplace
for i in range(K, N):
b, a = problems[i]
if a > heap[0]:
total += a - replace(heap, a)
if total >= P:
print(b)
return
print(-1)
if __name__ == "__main__":
main()
この解説は gpt-6-astra-medium によって生成されました。
投稿日時:
最終更新: