Project Euler Problem 84 (C++和Python代码实现和解析)***

100 篇文章 3 订阅
87 篇文章 1 订阅

Problem 84 : Monopoly odds

In the game, Monopoly, the standard board is set up in the following way:
在这里插入图片描述
A player starts on the GO square and adds the scores on two 6-sided dice to determine the number of squares they advance in a clockwise direction. Without any further rules we would expect to visit each square with equal probability: 2.5%. However, landing on G2J (Go To Jail), CC (community chest), and CH (chance) changes this distribution.

In addition to G2J, and one card from each of CC and CH, that orders the player to go directly to jail, if a player rolls three consecutive doubles, they do not advance the result of their 3rd roll. Instead they proceed directly to jail.

At the beginning of the game, the CC and CH cards are shuffled. When a player lands on CC or CH they take a card from the top of the respective pile and, after following the instructions, it is returned to the bottom of the pile. There are sixteen cards in each pile, but for the purpose of this problem we are only concerned with cards that order a movement; any instruction not concerned with movement will be ignored and the player will remain on the CC/CH square.

Community Chest (2/16 cards):

  1. Advance to GO
  2. Go to JAIL

Chance (10/16 cards):

  1. Advance to GO
  2. Go to JAIL
  3. Go to C1
  4. Go to E3
  5. Go to H2
  6. Go to R1
  7. Go to next R (railway company)
  8. Go to next R
  9. Go to next U (utility company)
  10. Go back 3 squares.

The heart of this problem concerns the likelihood of visiting a particular square. That is, the probability of finishing at that square after a roll. For this reason it should be clear that, with the exception of G2J for which the probability of finishing on it is zero, the CH squares will have the lowest probabilities, as 5/8 request a movement to another square, and it is the final square that the player finishes at on each roll that we are interested in. We shall make no distinction between “Just Visiting” and being sent to JAIL, and we shall also ignore the rule about requiring a double to “get out of jail”, assuming that they pay to get out on their next turn.

By starting at GO and numbering the squares sequentially from 00 to 39 we can concatenate these two-digit numbers to produce strings that correspond with sets of squares.

Statistically it can be shown that the three most popular squares, in order, are JAIL (6.24%) = Square 10, E3 (3.18%) = Square 24, and GO (3.09%) = Square 00. So these three most popular squares can be listed with the six-digit modal string: 102400.

If, instead of using two 6-sided dice, two 4-sided dice are used, find the six-digit modal string.

1. 欧拉项目第84道题 大富翁游戏的几率

玩家从GO开始,每次将两个六面骰子的点数相加来决定他下一步走向的格子(顺时针方向)。如果没有其他规则的话,我们可以预料每个格子被访问到的几率是相等的:2.5%。但是,如果落到格子G2J(去监狱JAIL),格子CC(社区福利基金),格子CH(机会)的话,这个分布就会被改变。

除了G2J以及CC和CH中的一张牌能够让玩家直接进监狱(JAIL)以外,如果玩家连续三次掷骰子得到两个相同的数字,那么第三次玩家也将直接进监狱。

在游戏开始时,CC和CH中的牌是乱序排放的。当玩家落到CC或CH时,他从相应的牌堆顶上拿一张牌,并执行完下面的指令后,将牌放回到牌堆的底部。每个牌堆中有16张牌,但是简单起见我们在此题目中只考虑涉及到移动的牌,任何不涉及到移动的牌将会被忽略,而且玩家仍然将处于CC/CH格子上。

CC (2/16 张牌):

  1. 去到 GO
  2. 去到 JAIL

CH (10/16 张牌):

  1. 去到 GO
  2. 去到 JAIL
  3. 去到 C1
  4. 去到 E3
  5. 去到 H2
  6. 去到 R1
  7. 去到下一个 R (铁路公司)
  8. 去到下一个 R
  9. 去到下一个 U (公用公司)
  10. 回退3格

这个题目的核心问题是每个格子被访问到的可能性。也就是每次掷骰子之后落到某个格子的几率。因此,除了落到G2J的几率为零以外,落到CH的几率最小,因为落到CH后有5/8的几率会再次移动到其他格子。而我们感兴趣的是每次掷骰子之后最终落到的那个格子。我们对“只是访问”和被送到JAIL不作区分,我们也同时忽略需要掷一个double(两个相同的数字)才能走出JAIL的规则,只是假设玩家下一轮自动走出监狱。

从GO开始对格子编号(00到39),我们将这些两位数的数字相连接来得到与格子的集合相对应的字符串。

从统计上来说可以得到三个最受欢迎的格子按顺序是,JAIL(6.24%)= 10号格子,E3(3.18%)= 24号格子,GO(3.09%)= 00号格子。所以这三个最受欢迎的格子可以用一个六位的字符串表示:102400。

如果我们用两个4面的骰子代替两个6面的骰子,找出代表三个最受欢迎格子的六位字符串。

2. 求解分析

这道题就是模拟大富翁游戏, 所以我们投掷骰子的时候, 使用随机函数来模拟点数。

首先,我们要有一个棋盘,里面有40个格子(square), 从00到39格子依次为
‘GO’, ‘A1’,‘CC1’,‘A2’, ‘T1’,‘R1’,‘B1’, ‘CH1’,‘B2’,‘B3’,‘JAIL’,‘C1’,‘U1’, ‘C2’, ‘C3’, ‘R2’, ‘D1’, ‘CC2’,‘D2’,‘D3’, ‘FP’, ‘E1’,‘CH2’,‘E2’, ‘E3’,‘R3’,‘F1’, ‘F2’, ‘U2’,‘F3’, ‘G2J’, ‘G1’,‘G2’, ‘CC3’,‘G3’,‘R4’,‘CH3’,‘H1’, ‘T2’,‘H2’。如果,我们用数组或列表来表示,那么索引依次为0~39,其中, ‘GO’ 是 0, ‘JAIL’ 是10等。

然后,当我们投骰子的时候,我们拿到两次骰子的点数之和,但如果连续三次两个骰子的点数相同的话,点数直接为-1, 最终是去’JAIL’的。

每次,投骰子后,玩家的位置是变化的,根据骰子的点数以及新的格子的要求移动。

如果,格子是CC1,CC2或CC3,那么随机从16张牌抽一张牌:

  1. 去到 GO
  2. 去到 JAIL

如果,格子是CH1, CH2或CH3,那么随机从16张牌抽一张牌:

  1. 去到 GO
  2. 去到 JAIL
  3. 去到 C1
  4. 去到 E3
  5. 去到 H2
  6. 去到 R1
  7. 去到下一个 R (铁路公司)
  8. 去到下一个 R
  9. 去到下一个 U (公用公司)
  10. 回退3格

如果,格子是G2J,那么直接去格子’JAIL’。

然后,我们要基于每个格子统计走过的频率,是用Python dictionary或者C++ map就可以实现。

最后,我们需要对Python dictionary或者C++ map排序,关键字是频率。当然,我们需要统计的是出现百分比最高的三个格子的位子的字符串,不难做一下变换求得。

值得说一下的是,我们的样本总量需要很大,这个数值可以试几次最后确定,我取值为2000000。

3. C++ 代码实现

C++ 跟Python都遵循2.求解思路,代码实现的思路也差不多,也使用了同样的函数名。C++使用Debug模式稍慢,如果是Release模式,很快,原因是我们是用了map和 vector,模拟了2000000次。

类图如下:
在这里插入图片描述

C++代码 (需要支持C++11)

#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <algorithm>
#include <cstdlib>

using namespace std;

class SquareFrequency
{
public:
    string m_square;
    int    m_frequency;
    // float  m_rate;   // m_rate = 1.0*m_frequency / max_tries
    string m_pos_str;

    bool operator > (const SquareFrequency& sf) const
    {
        return m_frequency > sf.m_frequency;
    }
};

class PE0084
{
private:
    static const int m_max_cards = 16;
    int m_consecutive_doubles = 0;

    vector<string> m_squares_vec = {"GO", "A1", "CC1",  "A2", "T1", "R1", "B1", "CH1", \
                                    "B2", "B3", "JAIL", "C1", "U1", "C2", "C3", "R2",  \
                                    "D1", "CC2", "D2",  "D3", "FP", "E1", "CH2", "E2", \
                                    "E3", "R3",  "F1",  "F2", "U2", "F3", "G2J", "G1", \
                                    "G2", "CC3", "G3",  "R4", "CH3","H1", "T2",  "H2"};

    // Community Chest (2 / 16 cards) :
    vector<int> m_cc_vec = { 0, 10 }; //'X','X','X','X','X','X','X','X','X','X','X','X','X','X'

    // Chance (10 / 16 cards) :
    vector<int> m_chances_vec = {0,10,11,24,39,5}; //'R*','R*','U*','-3','X','X','X','X','X','X'

    int get_next_R_pos(int pos);
    int get_next_U_pos(int pos);
    int roll_dices(int sides);

public:
    string get_six_digit_modal_string(int sides);
};

int PE0084::get_next_R_pos(int pos)
{
    // if CH1(7),  next R is R2(15)
    // if CH2(22), next R is R3(25)
    // if CH3(36), next R is R1(5)
    return (7 == pos) ? 15 : (22 == pos ? 25 : 5);
}

int PE0084::get_next_U_pos(int pos)
{
    // if CH2(22), next U is U2(28), 
    // if CH1(7),  next U is U1(12)
    return (22 == pos) ? 28 : 12;
}
    
int PE0084::roll_dices(int sides)
{
    // if a player rolls three consecutive doubles, they do not advance the  
    // result of their 3rd roll.Instead they proceed directly to jail.

    int d1 = rand() % sides + 1;
    int d2 = rand() % sides + 1;

    m_consecutive_doubles = (d1 == d2) ? (m_consecutive_doubles+1) : 0;

    return (m_consecutive_doubles>0 && m_consecutive_doubles%3==0) ? -1 : (d1+d2);
}

string PE0084::get_six_digit_modal_string(int sides)
{    
    map<string, int> squares_frequency_mp;
    int pos = 0, max_tries = 2000000;
    int scores, cardId;

    for (int roll_seq = 0; roll_seq < max_tries; roll_seq++)
    {
        scores = roll_dices(sides);
        if (scores > 0)
        {
            pos = (pos + scores) % 40;
            if (m_squares_vec[pos][0] == 'C' && m_squares_vec[pos][1] == 'H')
            {
                cardId = rand() % m_max_cards + 1;
                if (10 == cardId)
                {
                    pos = (pos - 3 + 40) % 40;
                }
                else if (cardId == 7 || cardId == 8)
                {
                    pos = get_next_R_pos(pos);
                }
                else if (9 == cardId)
                {
                    pos = get_next_U_pos(pos);
                }
                else if (cardId < 7)
                {
                    pos = m_chances_vec[cardId - 1];
                }
            }
            else if (m_squares_vec[pos][0] == 'C' && m_squares_vec[pos][1] == 'C')
            {
                cardId = rand() % m_max_cards + 1;
                if (1 == cardId || 2 == cardId)
                {
                    pos = m_cc_vec[cardId - 1];
                }
            }
            else if (m_squares_vec[pos] == "G2J")
            {
                pos = 10;    // 'JAIL'
            }
        }
        else
        {
            pos = 10;        // 'JAIL'
        }
    
        squares_frequency_mp[m_squares_vec[pos]] += 1;
    }
    
    vector<SquareFrequency> squares_numbering_vec;
    SquareFrequency sf;

    for (int i = 0; i < 4; i++)
    {
        for (int j = 0; j < 10; j++)
        {
            pos = i * 10 + j;   // square index in m_squares_vec
            sf.m_square    = m_squares_vec[pos];
            sf.m_frequency = squares_frequency_mp[m_squares_vec[pos]];
            sf.m_pos_str    = to_string(i) + to_string(j);
            squares_numbering_vec.push_back(sf);
        }
    }
    
    // sort by descending order
    sort(squares_numbering_vec.begin(), 
         squares_numbering_vec.end(), greater<SquareFrequency>());

    string digit_modal_str = "";
    for (int i = 0; i < 3; i++)  // top three max frequency squares
    {
        digit_modal_str += squares_numbering_vec[i].m_pos_str;
    }

    return digit_modal_str;
}
    
int main()
{
    PE0084 pe0084;

    string ans = pe0084.get_six_digit_modal_string(6);
    cout << "After two 6-sided dices are used, ";
    cout <<  "the six-digit modal string is " << ans << "." << endl;

    ans = pe0084.get_six_digit_modal_string(4);
    cout << "After two 4-sided dices are used, ";
    cout << "the six-digit modal string is " << ans << "." << endl;

    return 0;
}

4. Python 代码实现

Python代码实现的时候, 定义了几个函数:

  • get_next_R_pos(pos):根据当前的位置求下一个铁路公司的位置
  • get_next_U_pos(pos):根据当前的位置求下一个公用公司的位置
  • roll_dices(sides) : 投两个骰子,返回值为-1,或者 两个骰子的点数之和
  • get_six_digit_modal_string(sides): 返回频率前三位的格子的位置组成的字符串

Python 代码

from random import randint
    
def get_next_R_pos(pos):
    """ if CH1(7),  next R is R2(15)
        if CH2(22), next R is R3(25)
        if CH3(36), next R is R1(5) 
    """
    return 15 if pos == 7 else 25 if pos == 22 else 5
            
def get_next_U_pos(pos):
    """if CH2(22), next U is U2(28), 
       if CH1(7),  next U is U1(12)
    """
    return 28 if pos == 22 else 12 

def roll_dices(sides):
    """ if a player rolls three consecutive doubles, they do not advance the  
    result of their 3rd roll. Instead they proceed directly to jail.
    """
    global consecutiveDoubles
    d1 = randint(1, sides)
    d2 = randint(1, sides)
    if d1 == d2:
        consecutiveDoubles += 1
    else:
        consecutiveDoubles = 0
    
    return -1 if consecutiveDoubles>0 and consecutiveDoubles%3==0 else d1+d2
    
def get_six_digit_modal_string(sides):
    squares_name_list  = ['GO',  'A1','CC1','A2', 'T1','R1','B1', 'CH1','B2','B3']
    squares_name_list += ['JAIL','C1','U1', 'C2', 'C3','R2','D1', 'CC2','D2','D3']
    squares_name_list += ['FP',  'E1','CH2','E2', 'E3','R3','F1', 'F2', 'U2','F3']
    squares_name_list += ['G2J', 'G1','G2', 'CC3','G3','R4','CH3','H1', 'T2','H2']

    # Community Chest (2/16 cards) : 
    cc_list      = [0, 10] #, 'X','X','X','X','X','X','X','X','X','X','X','X','X','X']
    # Chance (10/16 cards):
    chances_list = [0, 10, 11, 24, 39, 5] #,'R*','R*','U*','-3','X','X','X','X','X','X'] 
    
    pos, max_tries = 0, 2*10**6
        
    squares_frequency_dict = {}
    for i in range(40):
        squares_frequency_dict[squares_name_list[i]] = 0

    for roll_seq in range(0, max_tries):
        points = roll_dices(sides)
        if points > 0:
            pos = (pos + points) % 40
            if squares_name_list[pos][:2] == 'CH':
                cardId = randint(1, 16)
                if cardId == 10:
                    pos = (pos-3+40) % 40    
                elif cardId == 7 or cardId == 8:
                    pos = get_next_R_pos(pos)
                elif cardId == 9:
                    pos = get_next_U_pos(pos)
                elif cardId < 7:        
                    pos = chances_list[cardId-1]
            elif squares_name_list[pos][:2] == 'CC':
                cardId = randint(1, 16)
                if cardId == 1 or cardId == 2:
                    pos = cc_list[cardId-1]
            elif squares_name_list[pos] == 'G2J':
                pos = 10    # 'JAIL'
        else:
            pos = 10        # 'JAIL'
        
        squares_frequency_dict[squares_name_list[pos]] += 1
    
    for square in squares_name_list:
        squares_frequency_dict[square] = 100*squares_frequency_dict[square] / max_tries

    squares_numbering_dict = {}
    for i in range(4):
        for j in range(10):
            pos = i*10+j
            squares_numbering_dict[squares_name_list[pos]] = str(i)+str(j)

    my_list = sorted(squares_frequency_dict.items(), key=lambda x:x[1], reverse=True)
    #print(my_list)

    squares_count, numbering_list = 0, []
    for item in my_list:
        square_name, squares_count = item[0], squares_count + 1
        numbering_list += [ squares_numbering_dict[square_name] ] 
        if squares_count == 3:
            break

    digit_modal_s = ''.join(numbering_list)    
    #print(digit_modal_s)

    return digit_modal_s
    
def main():
    consecutiveDoubles = 0    

    print("After two 6-sided dices are used, ", end='')
    print("the six-digit modal string is %s." % (get_six_digit_modal_string(6)))

    print("After two 4-sided dices are used, ", end='')
    print("the six-digit modal string is %s." % (get_six_digit_modal_string(4)))

if  __name__ == '__main__':
    main()
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值