您的位置:首页 > 编程语言 > C语言/C++

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

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,这样可以下一次矩阵相乘时,它们的概率不会丢失(应该算做转移到自身)。如此连续相乘,求取矩阵的极限,能够获取最终的概率。而程序不可能求取无数次,而且也不必要求无数次矩阵相乘。就可以使得概率的收敛达到理想效果。下面是我的代码:

#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 //提示:自动阅卷结束唯一标识,请勿删除或增加。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息