D - 配送センターの荷物割り当て / Package Assignment at the Distribution Center Editorial by admin Editorial for printing
Claude 4.6 Opus (Thinking)概要
\(N\) 台のトラック(各トラックに重量上限と個数上限あり)に \(M\) 個の荷物をできるだけ多く割り当てる問題。トラックと荷物をソートし、貪欲法で最大個数を求める。
考察
重要な気づき
まず、全探索で全ての割り当てパターンを試すのは \(O(N^M)\) 程度になり、制約(\(N, M \leq 2 \times 10^5\))では到底間に合いません。
ここで貪欲法を考えます。鍵となる観察は以下です:
- 軽い荷物ほど柔軟:重量が小さい荷物は、重量上限 \(E_i\) が小さいトラックにも大きいトラックにも載せられます。
- 重い荷物は制限が厳しい:重量が大きい荷物は、\(E_i\) が十分大きいトラックにしか載せられません。
貪欲の方針
トラックを重量上限 \(E_i\) が小さい順に処理します。各トラックを処理する時点で「載せられる荷物の候補」をプールに追加していきます。
プールからどの荷物を選ぶべきか? 答えは「最も軽い荷物を優先的に使う」です。
理由:今のトラック(\(E_i\) が小さい)に載せられる荷物は、今後処理するトラック(\(E_i\) がより大きい)にも必ず載せられます。一方、まだプールに入っていない重い荷物は、\(E_i\) が大きい将来のトラックにしか載せられません。したがって、将来のトラックの積載枠を重い荷物のために残すため、今のトラックには軽い荷物を割り当てるのが最適です。
具体例
トラックA(\(E=5, C=2\))、トラックB(\(E=10, C=1\))、荷物の重量が \([3, 4, 8]\) の場合:
- トラックA処理時:プールに \(\{3, 4\}\) が入る → 軽い順に2個(\(3, 4\))を割り当て
- トラックB処理時:プールに \(\{8\}\) が追加 → 1個(\(8\))を割り当て
- 合計3個配送可能
もしトラックAで重い方を取ると、荷物8がトラックBの枠を奪い、合計が減る可能性があります。
アルゴリズム
- トラックを \(E_i\) の昇順にソートする
- 荷物を重量 \(D_j\) の昇順にソートする
- ソート済み荷物を管理するポインタ
pkg_idxを用意する - トラックを順に処理:
- \(D_{pkg\_idx} \leq E_i\) を満たす荷物を全て「利用可能プール」に追加
- プールから最も軽い \(\min(C_i, |プール|)\) 個を取り出し、配送個数に加算
- 最終的な配送個数を出力
計算量
- 時間計算量: \(O((N + M) \log M)\)
- ソート:\(O(N \log N + M \log M)\)
- 各トラック処理時のプールへの追加:全体で \(O(M \log M)\)(SortedListへの挿入1回が \(O(\log M)\))
- プールからの削除:全体で \(O(M)\)(SortedListのスライス削除は削除個数に比例、全体で最大 \(M\) 個削除)
- 空間計算量: \(O(N + M)\)
実装のポイント
SortedList の活用:Pythonの
sortedcontainers.SortedListを使うと、要素の追加が \(O(\log M)\)、先頭からのスライス削除del available[:take]が効率的に行える。ソート順の一致:トラックも荷物も昇順にソートすることで、ポインタを一方向に進めるだけで「載せられる荷物の候補追加」を実現できる(尺取り法的な発想)。
個数最大化に特化:荷物の重量の合計制約がないため、個々の荷物が載るかどうかだけを判定すればよく、ナップサック問題のような複雑さは不要。
ソースコード
import sys
from sortedcontainers import SortedList
def main():
input_data = sys.stdin.buffer.read().split()
idx = 0
N = int(input_data[idx]); idx += 1
M = int(input_data[idx]); idx += 1
trucks = []
for i in range(N):
E = int(input_data[idx]); idx += 1
C = int(input_data[idx]); idx += 1
trucks.append((E, C))
D = []
for j in range(M):
D.append(int(input_data[idx])); idx += 1
# Greedy approach:
# Sort trucks by E (weight limit) in ascending order.
# Sort packages by weight in ascending order.
# Process trucks in order of increasing E.
# For each truck, all packages with D_j <= E_i become available.
# Among available (unassigned) packages, we want to pick the C_i lightest ones
# (greedy: use lightest packages for this truck, saving heavier ones for trucks with higher E).
# Actually, we should pick the HEAVIEST available packages that fit, because
# lighter packages are more flexible (can fit in more trucks).
# Wait, let me reconsider...
# Actually the optimal greedy: sort trucks by E ascending. For each truck,
# among all available packages with D_j <= E_i, we should assign the HEAVIEST ones
# to this truck. Because lighter packages can potentially fit in trucks with smaller E
# that we haven't... wait, we process in ascending E order, so all future trucks have
# E >= current E, meaning lighter packages will also fit in future trucks.
# So we should assign the heaviest available packages (that fit) to the current truck,
# keeping lighter ones for potentially being used by future trucks (which they'll fit in too).
# But future trucks also accept heavier packages...
# Actually, the standard greedy for this type of problem:
# Sort trucks by E ascending, sort packages by D ascending.
# For each truck (ascending E), add all packages with D <= E to available pool.
# Then greedily take the C_i smallest from the pool (to leave heavier for bigger trucks).
# No wait... we want to maximize count, not minimize waste.
# To maximize the number of packages delivered:
# Sort trucks by E ascending. Sort packages by D ascending.
# Maintain a pool of available packages. For each truck, add packages with D <= E.
# From the pool, take up to C_i packages. Which ones? The lightest, because
# heavier packages in the pool can only be used by trucks with E >= current E (future trucks),
# so we should save them. Lighter packages can also be used by future trucks, but
# by using them now we free up capacity for heavier packages later.
# Actually since all packages in the pool fit in the current truck AND all future trucks,
# it doesn't matter which we pick for count maximization...
# But packages added later (heavier) only fit in future trucks, not current.
# So we should use the lightest ones now (they fit everywhere) to save capacity of
# future trucks for heavier packages that only fit there.
# Yes: pick the lightest C_i from the pool.
trucks.sort(key=lambda x: x[0])
D.sort()
# We need a data structure that supports:
# - Adding elements
# - Removing the smallest C_i elements efficiently
# Use a SortedList and pop from the front
available = SortedList()
pkg_idx = 0
total = 0
for E, C in trucks:
# Add all packages with D <= E
while pkg_idx < M and D[pkg_idx] <= E:
available.add(D[pkg_idx])
pkg_idx += 1
# Take up to C lightest packages
take = min(C, len(available))
if take > 0:
total += take
# Remove the smallest 'take' elements
# SortedList supports slicing deletion
del available[:take]
print(total)
if __name__ == '__main__':
main()
この解説は claude4.6opus-thinking によって生成されました。
posted:
last update: