抛硬币的赌博游戏——庞果英雄会

         这是一道来自庞果网的在线编程挑战题目,属于中等偏上难度的题目。正式这道题目,让我对庞果网的程序挑战产生了兴趣。下面就讲一下我的解题思路。

         题目:抛硬币的赌博游戏

         原文链接(有可能失效):http://hero.pongo.cn/OnlineCompiler/Index?ID=59&ExamID=57

         题目详情:小a和小b起初分别有A块钱和B块钱,它们决定玩一个赌博游戏,游戏规则是扔一个硬币,如果结果是正面的话,小a要给小b C块钱。

否则是反面的话,小b给小a D块钱。

         它们不断地扔硬币,直到某一次应该给钱的人拿不出那么多钱,就认为他破产输掉了。

硬币不是均匀的,它以p1的概率产生正面,1 - p1的概率产生反面。

         请问小a最后胜利(也就是说小b破产输掉)的概率有多大?

         输入:A,B,C,D是整数,0<=A,B<50,0<=C,D<=100,p1是浮点数 0<=p1<=1;

         输出:为保证输出的是整数,请输出小a获胜的概率乘以100后再下取整(直接截断后面的位数)的结果。

         例如,结果是0.125则输出12。

 

         一看这道题目,是计算概率,但利用数学公式去计算,却无比的麻烦。于是拿出一个例子来分析,应该能找到这道题目的解决方法。那么,假设经过若干次抛硬币,小a持有x元钱,小b持有y元钱,那么将这个时刻看作一种状态,那么下一次如果还可以继续投硬币(即:x不小于0,y不小于0),则投掷一次硬币,可能变成小a持有x-2元,小b持有y+2元,这又是一种状态,与之对应,小a持有x+1元,小b持有y-1元,这是另外一种状态。由此,我们可以将整个抛硬币的过程看作状态之间的转换。那么,状态是否有无限种?状态之间的转换有什么关系呢?下面,就一组给定的数据,画出状态的转换图。假定开始小a有5元,小b有3元,硬币朝上,小a给小b的钱数为2元,硬币朝下,小b给小a的钱数为1元。


在这里,椭圆中第一个数字为小a持有的钱数,第二个数字为小b持有的钱数。将小a或者小b持有钱数为负值的情况作为终点状态,因为此时有一个人已经输掉所有钱,不会再有下一种状态了。而如图,两种相邻的状态,他们的子状态,有一个是重合的,如状态(3/5)可以转换成(4/4),与它相邻的状态(6/2)也可以转换成(4/4),可以把它们合并。这是状态转换的一个特点。

         随着状态转换的次数增加,可以看出,最后出现的状态总是与前边的状态重复,这说明,所有的状态应该是有限的。图中,再往后的状态转换没有画出,因为,状态已经重复了,不可能再出现新的状态了。

         下面考虑一下概率的问题,最终我们要得到的概率。假设硬币朝上概率为0.7。起始状态(5/3)的概率肯定是1,那么硬币第一次投硬币之后,(3/5)的概率为0.7,而(6/2)的概率为。我们可以认为,子节点的概率,来源于父节点的概率。节点的两个字节点的概率,分别等于该节点的概率乘以0.7和0.3。于是概率计算的问题也解决了。

         随着状态的变化,概率发生了转移。再次观察状态转换图,如果我们要计算状态(3/5)的概率需要找到状态(5/3)和状态(2/6)这样,P(3/5) = P(5/3) * 0.7 + P(2/6) * 0.3;计算一个节点的公式我们得出了,那么将公式堆叠在一起,我们可以得到一个矩阵。如下图:


图中,如果矩阵的右边乘以当前每个状态点概率的列向量,则获取下一次抛一次硬币时各个状态的概率。在这里,我将状态(-2/10)、(-1/9)、(9/-1)三个点自身提供给自身的概率设置为1,这样可以下一次矩阵相乘时,它们的概率不会丢失(应该算做转移到自身)。如此连续相乘,求取矩阵的极限,能够获取最终的概率。而程序不可能求取无数次,而且也不必要求无数次矩阵相乘。就可以使得概率的收敛达到理想效果。下面是我的代码:

#include <cstdio>
#include <string>
#include <cmath>

using namespace std;

struct Node{
    int A; // 小a剩余钱数
    int B; // 小b剩余钱数
    int finish; // 是否终结,-1小a输掉,1,小b输掉,0未达到输赢
    struct Node* next;

};


int win(int A,int B,int C,int D,double p1) {

    double pA = 0.0;
    double p2 = 1 - p1;

    if(A == B && C == D && p1 == 0.5){
        pA = 0.5; // 双方条件一致,概率一样
    }else if(A < C && B < D){
        pA = 1 - p1; // 一局定胜负
    }else{
        int start = - C; // 小a拥有的最小的财富,可能是负值
        int end = A + B + D; // 小a拥有的最大财富,小b可能是负值
        int count = end - start + 1;
        bool* exists = new bool[count];
        int i = 0 , j = 0;

        for(i = 0 ; i < count ; ++i){ // 初始化变量,全部置为false
            exists[i] = false;
        }
        // 确定根节点
        Node *root = new Node();
        root -> A = A;
        root -> B = B;
        root -> finish = 0;
        

        int current = 0; // 当前第几个节点
        int total = 1; // 节点总个数

        Node * front = root;
        Node * tail = root;
        Node * pNew = NULL;

        exists[root -> A + C] = true;

        int finish = 0;
        int nextA = 0;
        int nextB = 0;

        while(current < total){
            
            if(front -> finish == 0){
                // 处理正面的情况
                nextA = front -> A - C;
                nextB = front -> B + C;

                if(nextA < 0){
                    finish = -1;
                }else {
                    finish = 0;
                }
                pNew = new Node();
                pNew -> A = nextA;
                pNew -> B = nextB;
                pNew -> finish = finish;

                if(!exists[nextA + C]){ // 节点未加入队列
                    tail -> next = pNew;
                    tail = tail -> next;
                    exists[nextA + C] = true;
                    total ++;

                }

                // 处理反面
                nextA = front -> A + D;
                nextB = front -> B - D;

                if(nextB < 0){
                    finish = 1;
                }else {
                    finish = 0;
                }
                pNew = new Node();
                pNew -> A = nextA;
                pNew -> B = nextB;
                pNew -> finish = finish;

                if(!exists[nextA + C]){ // 节点未加入队列
                    tail -> next = pNew;
                    tail = tail -> next;
                    exists[nextA + C] = true;
                    total ++;

                }

            } // if判断finish==0

            front = front -> next; // 移动到下一个节点
            current ++;

        }// while循环

        //迭代生成概率向量
        int * tag = new int[count];

        int k = 0;
        for(i = 0 ; i < count ; ++i){
            
            if(exists[i]){
                tag[i] = k;
                k ++;
            }else{
                tag[i] = -1; // 标示该节点不存在
            }
            
        }

        double * pFir = new double[total];
        double * pSec = new double[total];
        double * pMid = NULL;

        int * numbers = new int[total]; // 小a可能拥有的钱数

        int r = 0 , n = 0 ; // r循环变量,n迭代次数

        front = root;
        while(front != NULL){ // 初始化小a可能出现的钱数(按照从小到大排列)
            numbers[tag[front -> A + C]] = front -> A;
            front = front -> next;
        }

        // 初始化概率
        for(r = 0 ; r < total ; ++ r){ 
            pFir[r] = 0.0;
            pSec[r] = 0.0;
            
        }
        
        pFir[tag[A + C]] = 1.0; // 起始概率为1

        int T = A + B;

        n = 0;

        double pSum = 0.0;

        int v = 0;

        while(n++ < 1000){ // 完成n次迭代(最多一万次)
            
            for(r = 0 ; r < total ; ++ r){ // 完成一次迭代

                if(numbers[r] < 0){ // 小a输掉的节点
                    pSec[r] = pFir[r] + pFir[tag[numbers[r] + C + C]] * p1;

                }else if(numbers[r] <= T){
                    
                    pSec[r] = 0;
                    if(numbers[r] - D >= 0){
                        pSec[r] += pFir[tag[numbers[r] - D + C]] * p2;

                    }
                    if(numbers[r] + C <= T){
                        pSec[r] += pFir[tag[numbers[r] + C + C]] * p1;

                    }
                    
                }else{
                    pSec[r] = pFir[r] + pFir[tag[numbers[r] - D + C]] * p2;                
                    
                }

            } // 完成一次迭代

            //交换概率
            pMid = pFir;
            pFir = pSec;
            pSec = pMid;

            pA = 0.0;
            pSum = 0.0;
            for(v = 0 ; v < total ; v ++){ // 计算新概率

                if(numbers[v] >= 0 && numbers[v] <= T){
                    pSum += pFir[v];
                }else if(numbers[v] > T){
                    pA += pFir[v];
                }
            }

            if(pSum < 0.00001){
                break;
            }

        } // 完成n次迭代

    }

    return pA * 100;

}



//start 提示:自动阅卷起始唯一标识,请勿删除或增加。
int main()
{   
    printf("%d" , win(5, 3, 2 , 1 , 0.7));

    //
    return 0;   
} 
//end //提示:自动阅卷结束唯一标识,请勿删除或增加。        



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
以下是使用Python实现的三个函数: ``` import random def coin_normal(players, rounds): player_funds = [10] * players while rounds > 0 and all([f > 0 for f in player_funds]): result = random.randint(0, 1) for i in range(players): if result == 0: player_funds[i] -= 10 else: player_funds[i] += 10 rounds -= 1 return player_funds def coin_battle(players, rounds): player_funds = [10] * players banker_funds = float('inf') while rounds > 0 and all([f > 0 for f in player_funds + [banker_funds]]): result = random.randint(0, 1) for i in range(players): if result == 0: player_funds[i] -= 10 banker_funds += 10 else: player_funds[i] += 10 banker_funds -= 10 rounds -= 1 return player_funds, banker_funds def coin_commission(players, rounds, cms): player_funds = [10] * players banker_funds = float('inf') while rounds > 0 and all([f > 0 for f in player_funds + [banker_funds]]): result = random.randint(0, 1) for i in range(players): if result == 0: player_funds[i] -= 10 banker_funds += 10 else: player_funds[i] += 10 banker_funds -= 10 commission = sum(player_funds) * cms banker_funds += commission rounds -= 1 return player_funds, banker_funds ``` `coin_normal`函数用于模拟每个赌徒与庄家进行公平抛硬币游戏,每个赌徒的赌本为10元,而庄家的赌本为无限大。函数的参数为赌徒人数和每个赌徒与庄家赌博的最大轮数。函数返回一个列表,其中每个元素表示每个赌徒最终的资金。 `coin_battle`函数用于模拟多个赌徒与一个庄家进行公平抛硬币游戏,每个赌徒和庄家的起始赌本都为10元。函数的参数为赌徒人数和每个赌徒与庄家赌博的最大轮数。函数返回一个元组,其中第一个元素为一个列表,表示每个赌徒最终的资金;第二个元素为庄家最终的资金。 `coin_commission`函数用于模拟多个赌徒与一个庄家进行公平抛硬币游戏,每个赌徒和庄家的起始赌本都为10元,而且赌徒赢了时,专家还抽取一定比例的抽成。函数的参数为赌徒人数、每个赌徒与庄家赌博的最大轮数和专家的抽成比例。函数返回一个元组,其中第一个元素为一个列表,表示每个赌徒最终的资金;第二个元素为庄家最终的资金。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值