Problem 84

这个游戏就是小时候玩的大富翁,掷骰子来决定前进步数,到了某个方格可能会触发一些事件,转移到某个方格。
问题如果无限玩这个游戏,那么走到哪三个格子的概率最大呢?

一个比较正规的做法是建立一个四十乘四十的矩阵,是概率转移矩阵,记录每个格子转移到其他格子的概率,这个矩阵自乘成千上万次收敛之后,用初始状态,一个四十维的向量,和矩阵相乘,得到最终的四十维的向量,选择三个概率最高的格子。

但是我觉得一个一个算概率太麻烦,下面采用模拟游戏的方法,掷骰子一百万次,然后计算每个格子的次数,得到一个近似概率。
下面写贴出整个代码,然后通过注释的方式解释玩法、算法。

private static readonly int Rolls = 1_000_000;

// some special positions
private static readonly int Go = 0;
private static readonly int Jail = 10;
private static readonly int Go2Jail = 30;
private static readonly int C1 = 11;
private static readonly int E3 = 24;
private static readonly int H2 = 39;
private static readonly int R1 = 5;
private static readonly int[] Community = new int[] { 2, 17, 33 };

// 有三个 Chance 点,后面两个下一站坐标和 Chance 是对应的
private static readonly int[] Chance = new int[] { 7, 22, 36 };
private static readonly int[] NextRailway = new int[] { 15, 25, 5 };
private static readonly int[] NextUtility = new int[] { 12, 28, 12 };

public string GetAnswer()
{
    // 记录每个到每个格子的次数
    int[] board = new int[40];
    Random random = new Random();
    int doubles = 0;
    int current = 0;

    // Index,指代特殊牌的位置,也是特殊牌的值,详见下面的注释
    int chanceIndex = 0;
    int communityIndex = 0;

    for (int i = 0; i < Rolls; i++)
    {
        // 这个题目和原始玩法不一样的地方,使用两个点数为 1-4 的骰子
        int dice1 = random.Next(1, 5);
        int dice2 = random.Next(1, 5);

        // 如果两个骰子点数一样,记作 double
        doubles = (dice1 == dice2) ? doubles + 1 : 0;
        if (doubles > 2)
        {
            // 如果连续三次 double,直接进监狱
            current = Jail;
            doubles = 0;
        }
        else
        {
            // 没有连续三次 double,前进若干步
            current = (current + dice1 + dice2) % 40;

            // 现在考虑到了各种特殊的格子
            //
            // 处理 Chance
            if (current == Chance[0] || current == Chance[1] || current == Chance[2])
            {
                // 使用一张特殊牌,切换位置
                // 按理说,这两个应该在这个 if 的最后
                // 但其实没有关系,先处理相当于整体牌的位置变化了一下而已
                chanceIndex++;
                chanceIndex %= 16;

                // 这里记录 index 是为了跳转到对应的 Railway 或者 Utility
                int index = 0;
                if (current == Chance[1])
                {
                    index = 1;
                }
                else if (current == Chance[2])
                {
                    index = 2;
                }

                // 十六张特殊的牌,关于牌的堆叠,比较好的做法是初始化 1-16,
                // 然后洗牌,在循环中摸牌,最后对各个点数进行处理
                // 我这里取巧了一下,假定牌就是按照 1-16 码放的,理论上随机很多次之后
                // 牌是如何码放的对最后结果没有影响
                // 为了简化 chanceIndex 的操作,1-16 映射到了 0-15
                switch (chanceIndex)
                {
                    case 0:
                        current = Go;
                        break;
                    case 1:
                        current = Jail;
                        break;
                    case 2:
                        current = C1;
                        break;
                    case 3:
                        current = E3;
                        break;
                    case 4:
                        current = H2;
                        break;
                    case 5:
                        current = R1;
                        break;
                    case 6:
                    case 7:
                        current = NextRailway[index];
                        break;
                    case 8:
                        current = NextUtility[index];
                        break;
                    case 9:
                        current -= 3;
                        break;
                }
            }

            // 处理 Community Chest
            // 这里的思路和处理 Chance 一致
            if (current == Community[0] || current == Community[1] || current == Community[2])
            {
                switch (communityIndex)
                {
                    case 0:
                        current = Go;
                        break;
                    case 10:
                        current = Jail;
                        break;
                }

                communityIndex++;
                communityIndex %= 16;
            }

            if (current == Go2Jail)
            {
                current = Jail;
            }
        }

        board[current]++;
    }

    // 玩了一百万个回合了,排序
    // 按照 Item 排序,Indx 记录原始的位置
    int[] order = board.Select((item, indx) => new { Item = item, Index = indx })
                       .OrderByDescending(x => x.Item)
                       .Select(x => x.Index)
                       .ToArray();

    // 选择前三个,拼接最后题目要求的字符串
    string modalString = "";
    for (int i = 0; i < 3; i++)
    {
        if (order[i] < 10)
        {
            modalString += "0";
        }

        modalString += order[i].ToString();
    }

    return modalString;
}

原文发表于我的小站

03-05 15:18