ナップサック問題?
某プログラム勉強会で、お題が出たので解いてみる。
あなたは硬貨を何枚か持っており、ある金額を払いたい。
お釣りの金額が最小となるような硬貨の組み合わせと、その場合のおつりを計算せよ。
硬貨の価値はすべて正の整数、払いたい金額も正の整数で、オーバーフローは考慮しなくても良いとする。
最良の組み合わせが複数ある場合、そのうちひとつを出力すればよい。
問題を読んだ瞬間「ナップサック問題?」と思ったけれど微妙に違う。
しかし、「持っている硬貨の金額合計−払う金額」を超えない最大硬貨の組み合わせと考えるとナップサック問題になる。
ナップサック問題の解法はネットにいくらでもあるし楽勝楽勝。と思っていた・・が。
よく見かける動的計画法を使ったアルゴリズムは同じ硬貨を何枚でも使っていい場合のみ適用できるので、この問題では使えない。
その他は遺伝的アルゴリズムとか必ずしも最適解を出すとは限らないものばかり。さすがNP困難の問題。
結局力技で解決。
コインの全ての組み合わせを生成してるので(多少枝狩りはしているが)コインの種類が多くなると遅くなる。
class Coins def initialize(total, coins) @total = total @coins = coins end def gen_sum(coins) if coins.size == 1 @used.push coins.first yield coins.first @used.pop yield 0 else car,*cdr = coins gen_sum(cdr) do |sum| next if sum > @sum_limit yield sum next if car + sum > @sum_limit @used.push car yield car + sum @used.pop end end end def solve @used = [] used_sv = nil min = @coins.first @sum_limit = @total + min - 1 gen_sum(@coins) do |sum| change = sum - @total next if change < 0 if change < min min = change used_sv = @used.clone end break if min == 0 end puts "金額 : %d, 支払い : %s, おつり : %d"%[@total, used_sv.sort.inspect, min] end end total, *coins = ARGV.map(&:to_i) Coins.new(total, coins.sort).solve