AT274の技術だったりなかったり

あっとのTECH LOG

競プロのこととか技術のこととか。たまに日常を書きます。

JOI難易度6 古本屋

問題原文

https://www.ioi-jp.org/joi/2010/2011-ho-tasks_data/2011-ho.pdf#page=4

問題要旨

 N 冊の本のうち、 K 冊を売ることを考える。 i 番目の本の買取価格は  a_i である。
また、本には10種類のジャンルがあって、同じジャンルの本をまとめて  T 冊売ると、1冊あたり  T - 1 円買取価格が上がる。
最適に売る本を選んだ時の、最大合計買取価格を求めよ。

  •   1 \leq N \leq 10^{5}
  •  1 \leq a_i \leq 10^{5}
  • 本のジャンルは最大10種類

解法

まず、あるジャンルについて売る冊数を決めたなら、買取価格が高い方から売っていくのがよい。
また、買取価格が高い方から  n 冊売った時の 合計買取価格は累積和を取っておくと都度高速にわかる。

ここまで浮かべば自然に dpが浮かんで、
 dp[g\rbrack[k\rbrack :=  g 種類目まで見て、 k 冊売っている時の、最大合計買取価格 」 という素直なdpテーブルで解ける。

遷移は「 g 種類目まで見て、」「ジャンル  g について  u冊売って」「これまでの合計  k 冊売ったことになるとき」の、最大合計買取価格、のような形で回るため、実際のループは3重になる。
ぱっと見間に合わなさそうだが、 u 冊売って〜の部分が結局合計で  N 回しか回らないので結局  O(NK) になる。(あってる。。。?)

実装

from itertools import accumulate
N, K = map(int, input().split())
Books = [[] for _ in range(10)]
for _ in range(N):
    c, g = map(int, input().split())
    g -= 1
    Books[g].append(c)

for g in range(10):
    Books[g].sort(reverse=True)
    Books[g] = [0] + list(accumulate(Books[g]))

# dp[g][k] := g種類目まで見て、k冊売っているときの最大値
dp = [[0] * (K + 1) for _ in range(11)]
for g in range(10):
    # u冊売る
    for u in range(len(Books[g])):
        for k in range(K + 1):
            if k + u > K:
                break
            dp[g + 1][k + u] = max(dp[g + 1][k + u], dp[g][k] + Books[g][u] + max(0, u - 1) * u)

print(dp[-1][-1])

感想

このdpを自然だと思えるようになったのは成長かなぁ。。。