全ての選出と交換に渡る全ての敵の組み合わせを計算 その10

ダム問題について考えていたところ、そういや1つの敵のエントリーの決定範囲には同じ種族が複数存在しないことが利用できるんじゃないかと思いつきました。
つまり、ダム問題においては、「グループAの各ダムとつながっている水道局はそれぞれ高々1個」としていいということ。その前提付きならカバーできる水道局の個数についての貪欲法でもうまくいくんじゃないかとやってみたところ、100個のランダムなケースで正しい結果が出ました。

これを利用して、1戦分の敵の組み合わせを計算するプログラムを書きました。

ところで、この記事の先頭の行を書いたときに、「あっ、ランクが大きくなると準伝説が出てくるので"1つの敵においてエントリーの決定範囲には同じ種族が複数存在しない"は成立しないじゃん!」と気がつきました…orz

def predict0(prng, skipped, chosen)
  if chosen.length == nParty
    return [chosen].to_set
  end
  # 3つのアイテムと種族を選んでskippedをカバーすることができないなら、
  # skippedの全エントリーがplayersと衝突するような3つのエントリーplayersは
  # 存在しないということなので、このルートはありえないとみなし空集合を返す
  if not coverable?(skipped)
    return [].to_set
  end
  prngp, x = choose_entry(prng)
  if x.collides_within?(chosen)
    # 既に採用済みの場合
    predict0(prngp, skipped, chosen)
  elsif skipped.include?(x)
    # まったく同じエントリをスキップしたことがある場合
    predict0(prngp, skipped, chosen)
  else
    result1 = predict0(prngp, skipped, chosen + [x])
    result2 = predict0(prngp, skipped + [x], chosen)
    result1 + result2
  end
end

# アイテムと種族をそれぞれ3つずつ適当に選べば、それらでentriesをカバーできるかを判定する
def coverable?(entries)
  coverd = Set.new
  selected_items = Set.new
  all_items = entries.map(&:item).to_set
  linked = Hash.new
  entries.each do |entry|
    (linked[entry.item] ||= Set.new).add entry
  end

  # カバーできるエントリーの個数についての貪欲法でアイテムを3つ選ぶ
  nParty.times do |i|
    items = all_items - selected_items
    break if items.empty?
    item = items.max_by {|item|
      (linked[item] - coverd).size
    }
    selected_items.add item
    coverd += linked[item]
  end
  # 残っているエントリーが3つ以下なら、それらの種族3つ選べばすべてカバーできることになる
  (entries.to_set - coverd).size <= 3
end
筆者: oupo (連絡先: oupo.nejiki@gmail.com)