解きました。フィードバックは大変なんでしょうね~。
最初に書いたコードでは、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"
}