抛硬币的赌博游戏——庞果英雄会
2013-08-02 19:49
495 查看
这是一道来自庞果网的在线编程挑战题目,属于中等偏上难度的题目。正式这道题目,让我对庞果网的程序挑战产生了兴趣。下面就讲一下我的解题思路。
题目:抛硬币的赌博游戏
原文链接(有可能失效):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,这样可以下一次矩阵相乘时,它们的概率不会丢失(应该算做转移到自身)。如此连续相乘,求取矩阵的极限,能够获取最终的概率。而程序不可能求取无数次,而且也不必要求无数次矩阵相乘。就可以使得概率的收敛达到理想效果。下面是我的代码:
题目:抛硬币的赌博游戏
原文链接(有可能失效):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 //提示:自动阅卷结束唯一标识,请勿删除或增加。
相关文章推荐
- 挑战编程:抛硬币赌博游戏【转】
- 杂谈:英雄联盟的惩罚与游戏态度
- 基于QML Pathview的大型游戏英雄联盟-预览界面
- 【图灵杯 A】谷神的赌博游戏
- [BZOJ4820] 硬币游戏 - 高斯消元
- 【SDOI2017】硬币游戏
- 大脑的「行为性」成瘾,如赌博、购物、游戏、工作、饮食和性等成瘾,与毒品、药物类成瘾有什么区别?心理上有什么区别?
- 第二数学归纳法:硬币问题和堆垛游戏
- 庞果英雄会第二届在线编程大赛·线上初赛:AB数
- WPF真的很好用,重写了一下N年前让我戒掉赌博的游戏!!
- FZU - 1928 硬币翻转游戏(2维nim游戏)
- [KMP 高斯消元] BZOJ 4820: [Sdoi2017]硬币游戏
- 挑战庞果英雄会之最小操作数(失败求指教)
- 罐子与硬币--【英雄会】
- cocos2dx游戏--欢欢英雄传说--添加血条
- (八)在我们的游戏中添加硬币和障碍物
- 超好玩的硬币游戏,你会玩吗?
- 冲顶大会、百万英雄等答题游戏助手(Python脚本)
- Nim-硬币游戏2(Java)
- 10 craps赌博游戏