トップページに戻る    次の競技プログラミングの問題へ    前の競技プログラミングの問題へ

川添さん問題02 スロット・マシン

■■■問題■■■

     

n個のリール (数字が描かれている部分です) を持つスロットマシンを回します。
各リールには、0から9のいずれかの数字がランダムに出ます。
このとき、「最も多く出現した数字の出現回数」に等しいドルが賞金として得られます。

例えばn=6のスロットマシンを考えましょう。
各リールに、左から順に775107という数字が出たとします。
このとき、7の数字が最も多く3回出現していますので、賞金は3ドルです。

リールの数字が020926のとき、賞金は2ドルです。
リールの数字が123456のとき、賞金は1ドルです。
リールの数字が888888のとき、賞金は6ドルです。

このスロットマシンをn回まわしたときの賞金の期待値を F(n)とします。
例えば F(1) = 1,F(2) = 1.1,F(3) = 1.29 となることが確かめられます。

■ 第1問 (Normal)
F(6) の値を求めて下さい (四捨五入は不要です)

■ 第2問 (Hard)
F(12) の値を求めて下さい (四捨五入は不要です)


C#のソース

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main()
    {
        var sw = System.Diagnostics.Stopwatch.StartNew();

        for (int N = 1; N <= 12; N++) {
            //数字の組み合わせを列挙
            List<int[]> wkNumCombiArrList = DeriveNumCombiArrList(N);

            //数字の組み合わせごとの賞金合計を求める
            decimal Kitaiti = wkNumCombiArrList.Sum(X => DeriveSyoukinSum(X));

            Kitaiti /= (decimal)Math.Pow(10, N);
            Console.WriteLine("N={0}での期待値={1}", N, Kitaiti);
        }
        Console.WriteLine("経過時間={0}", sw.Elapsed);
    }

    struct JyoutaiDef
    {
        internal int CurrNum;
        internal List<int> NumList;
    }

    //数字の組み合わせを列挙
    static List<int[]> DeriveNumCombiArrList(int pN)
    {
        var WillReturn = new List<int[]>();

        var stk = new Stack<JyoutaiDef>();
        JyoutaiDef WillPush;
        for (int I = 0; I <= 9; I++) {
            WillPush.CurrNum = I;
            WillPush.NumList = new List<int>() { I };
            stk.Push(WillPush);
        }

        while (stk.Count > 0) {
            JyoutaiDef Popped = stk.Pop();

            //クリア判定
            if (Popped.NumList.Count == pN) {
                WillReturn.Add(Popped.NumList.ToArray());
                continue;
            }

            //Push処理
            for (int I = Popped.CurrNum; I <= 9; I++) {
                WillPush.CurrNum = I;
                WillPush.NumList = new List<int>(Popped.NumList) { I };
                stk.Push(WillPush);
            }
        }
        return WillReturn;
    }

    //数字の組み合わせごとの賞金合計を求める
    static decimal DeriveSyoukinSum(int[] pNumCombiArr)
    {
        //件数の配列
        int[] CntArr = pNumCombiArr.GroupBy(X => X).Select(X => X.Count()).ToArray();

        //最頻値の件数
        decimal ModeCnt = CntArr.Max();

        //同じものを含む順列の公式で件数を求める
        decimal PatternCnt = DeriveKaijyou(pNumCombiArr.Length);
        foreach (int EachInt in CntArr) {
            if (EachInt > 1) PatternCnt /= DeriveKaijyou(EachInt);
        }

        return ModeCnt * PatternCnt;
    }

    //階乗を求める
    static decimal DeriveKaijyou(int pN)
    {
        decimal WillReturn = 1;
        for (int I = 1; I <= pN; I++) {
            WillReturn *= I;
        }
        return WillReturn;
    }
}


C#の実行結果

N=1での期待値=1
N=2での期待値=1.1
N=3での期待値=1.29
N=4での期待値=1.534
N=5での期待値=1.7879
N=6での期待値=2.01966
N=7での期待値=2.22019
N=8での期待値=2.399318
N=9での期待値=2.57237595
N=10での期待値=2.74868911
N=11での期待値=2.9288005274
N=12での期待値=3.10849098132
経過時間=00:00:09.1362083


解説

n=12の場合に、
順列で考えると、10の12乗通りありますので、計算量多すぎですが、
重複組み合わせで、10H12なら、(10-1+12)C12 = 293930通りですので、
ナイーブに解けると判断し、下記の手順で進めてます。

手順1
深さ優先探索で、数字の組み合わせを列挙
(計算量を減らすため、順列ではなく、組み合わせで列挙)

手順2
列挙した数字の組み合わせごとの賞金合計を求める。
最頻値の件数と、
列挙した数字の組み合わせで可能な順列の個数
の積を累計する。

手順3
手順2で求めた累計 / (10のn乗)
が期待値