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

あっとのTECH LOG

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

ABC145 E - All-you-can-eat

問題原文

E - All-you-can-eat

問題要旨

ナップサック問題を解いてください。
ただし、最後に入れる品物(その品物を入れるとナップサックの容量以上になる)は重さを無視して良いものとします。

考えたこと

  • ナップサック問題を普通に解いて、どの品物を入れたか復元。使ってないものから価値が最も高いものを選べばいいのでは?(嘘解法)(価値を先に考えた結果、ぴったり入れきってしまって全部入らない場合などがある)

難しいこと

  • 最後だけ特別扱いできる

考えるべきこと

解法1 二つのdpテーブルで挟む
  • 「最後にどの品物を入れるか」を全探索したい。
  • 「前から商品を見たDPテーブル( dp1)」と「後ろから商品を見たDPテーブル(dp2)」があれば、 商品  i を最後に入れる場合の価値の総和は、  max(dp1[i - 1\rbrack[j\rbrack + dp2[i + 1\rbrack[W -  i - 1\rbrack + v_i)(0 \leq j \leq W - 1)となり、高速に計算できる。
実装

dpテーブルをいつもより一つ分大きくとると書きやすい。

N, W = map(int, input().split())
items = [list(map(int, input().split())) for i in range(N)]

# dp1[i][j] := 商品0 ~ i が対象
# dp2[i][j] := 商品i ~ N が対象(順番はそのまま)
# out-of-index 対策に余分に取っておく
dp1 = [[0] * (W + 1) for i in range(N + 2)]
dp2 = [[0] * (W + 1) for i in range(N + 2)]

# dp1を埋める
for i in range(N):
    wi, vi = items[i]
    for j in range(W + 1):
        if j + wi <= W:
            dp1[i + 1][j + wi] = max(dp1[i + 1][j + wi], dp1[i][j] + vi)
        dp1[i + 1][j] = max(dp1[i][j], dp1[i + 1][j])

# dp2を埋める
for i in range(N + 1, 1, -1):
    wi, vi = items[i - 2]
    for j in range(W + 1):
        if j + wi <= W:
            dp2[i - 1][j + wi] = max(dp2[i - 1][j + wi], dp2[i][j] + vi)
        dp2[i - 1][j] = max(dp2[i][j], dp2[i - 1][j])


# 2つのdpテーブルを使って、最後に使う商品を全探索
ans = 0
# ここからは商品を1-indexedとする
for i, (wi, vi) in enumerate(items, start=1):
    for j in range(W):
        ans = max(ans, dp1[i - 1][j] + dp2[i + 1][W - j - 1] + vi)

print(ans)
解法2 ソートしてナップサック
  • よく考えると、「使う商品の中で、最も重たいものを最後に入れるのが最適」
  • つまり、あらかじめ入力を重さでソートしておけば、解法1の  dp1 のみで「各商品を最後に入れる場合の価値の総和の最大値」が求まる。
実装
N, W = map(int, input().split())
items = sorted([list(map(int, input().split())) for i in range(N)])

dp = [[0] * (W + 1) for i in range(N + 1)]

# dpテーブルを埋める
for i in range(N):
    wi, vi = items[i]
    for j in range(W + 1):
        if j + wi <= W:
            dp[i + 1][j + wi] = max(dp[i + 1][j + wi], dp[i][j] + vi)
        dp[i + 1][j] = max(dp[i][j], dp[i + 1][j])


ans = 0
# 最後に入れる商品は、残り容量1の時に入れるとしてしまってよい
for i, (wi, vi) in enumerate(items):
    ans = max(ans, dp[i][W - 1] + vi)

print(ans)

所感

「はさみこむdp」とかっていうのかな。いろんな解法に応用できそう。