codeiq @riverplusさんの 「スロット・マシン問題」を解いた。

解きました。フィードバックは大変なんでしょうね~。
最初に書いたコードでは、F(12)までとてもとてもまともな時間で計算できなかった。
想定時間30分で12のときまで解くのは結構ハードだと思う。

恥ずかしながら半日~1日くらい??

【問題】

n 個のリール(数字が描かれている部分です)を持つスロットマシンを回します。

各リールには、0 から 9 のいずれかの数字がランダムに出ます。

このとき、「最も多く出現した数字の出現回数」に等しいドルが賞金として得られます。

例えば n = 6 のスロットマシンを考えましょう。

各リールに、左から順に 7 7 5 1 0 7 という数字が出たとします。

このとき、7 の数字が最も多く 3 回出現していますので、賞金は 3 ドルです。

リールの数字が 0 2 0 9 2 6 のとき、賞金は 2 ドルです。

リールの数字が 1 2 3 4 5 6 のとき、賞金は 1 ドルです。

リールの数字が 8 8 8 8 8 8 のとき、賞金は 6 ドルです。

このスロットマシンを 1 回まわしたときの賞金の期待値を F(n) とします。

例えば F(1) = 1,F(2) = 1.1,F(3) = 1.29 となることが確かめられます。

■ 第1問 (Normal)

F(6) の値を求めて下さい(四捨五入は不要です)。

■ 第2問 (Hard)

F(12) の値を求めて下さい(四捨五入は不要です)。

【方針】
○ nをn以下で10個までの自然数の和に分割する方法が何通りあるかを計算する。
(このとき、同時に賞金ごとに分類しておく)
○ 上で計算した分割方法ごとに、リールの数字が何通りあるかを計算し、期待値を求める。
○ たとえば、n=6で4点のときは、[4, 1, 1], [4, 2] の2通りの分割があり、
それぞれ、 (10*9*8)*(6!/4!)/2!, (10*9)*(6!/4!/2!)/1!通りの組み合わせになる。
配列の長さmについて、(10からmとる順列)*(並べ方の数)/(配列中に同じ数字があれば割引く)通り。(うーん、我ながらわかりにくいか。)

【rubyの提出コード】

#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
BASE=10 # スロットが10進数で回る。
def solve(n)
  score_sum=0 # スコアの総和
  count_sum=0 # (デバッグ用)スロットの並び方の総和
  make_array(n).each{|score, arrays|
    arrays.each{|arr| # nと賞金で決まる配列ごとにループ
      # 対称性を考慮して、並べ方の数を数える。
      # 配列の中の同じ数字があるかどうか。
      sym=(1..n).map{|i| arr.count(i)>1 ? arr.count(i) : nil }
        .compact.map{|i| facto(i)}.reduce(&:*) || 1
      # 上の対称性も考慮した、並べ方の数。
      part = facto(n)/arr.map{|i| facto(i)}.reduce(&:*) / sym
      # 配列の中の数字にそれぞれ0-9の数字を当てはめる
      count = permu(arr.length) * part
      count_sum += count
      score_sum += score*count
    }
  }
  #  print "count(#{n}) = #{count_sum}\n"
  score_sum.to_f / BASE**n
end
# 普通の階乗計算
def facto(n)
  $facto||=[1]
  $facto[n]||=n*facto(n-1)
end
# BASE個の中からn個とる順列
def permu(n)
  ((BASE + 1 -n)..BASE).reduce(&:*)
end
# nをBASE個までの整数の和に分割し、賞金ごとに分けたHashに配列の配列として格納。
# たとえば n=3 のとき、{1=>[[1, 1, 1]], 2=>[[2, 1]], 3=>[[3]]}を返す。
def make_array(n)
  ret = Hash.new{|h,k| h[k]=[] }
  min = 1 + (n-1)/BASE
  (min..n).map{|sc|
    tmp = rec(sc,n).reject{|arr| arr.length > BASE }
    ret[sc].push(*tmp)
  }
  ret
end
# nと賞金が決まった時の、可能な自然数への分割を配列の配列で返す。
def rec(sc,n,ret=[],all=[])
  sum = ret.reduce(&:+) || 0
  num = n-sum
  if n == sum
    all << ret
  elsif num == sc
    ret << sc
    all << ret
  elsif sc == 1
    num.times{ ret << 1 }
    all << ret
  elsif num > 0 && num >= sc
    ret << sc
    (1..[sc,n-sc].min).each{|_sc|
      rec(_sc,n,ret.dup,all) # 次の数は直前に格納した数字と同じか少ない。
    }
  else
    nil
  end
  all
end

(1..20).each{|i|
  print "F(#{i}) = #{solve(i)}\n"
}

コメントを残す