挑战编程:抛硬币赌博游戏【转】



题目描述

小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

函数头部 int win(int A,int B,int C,int D,double p1);


题目的意思实际上是玩无限轮小a最后胜利的概率。这个概率由当前两人的钱数和输钱要给对方多少已经确定了。状态转移是比较好想的,但是这个转移是在图上是无向的(也就导致可以玩无限轮),要是直接算你会发现在无限递归。。。

有两种方法可以解决这个问题。


解法一:
引入轮数k,p[n][k]表示小a最初有n块钱,在k轮之内赢的概率。事实上,在k比较大时,p[n][k]收敛到在无限轮内赢的概率。
状态转移比较直接:如果n>=C,那么第一轮可以先输,后面k-1轮赢回来,这个概率是p1*p[n-C][k-1]。反之,只能第一轮就从小b那拿钱了,这个概率是(1-p1)* p[i+D][k-1](注意i+D>A+B时p[i+D][k-1]=1)。

这样很容易写出代码了,轮数取1万应该差不多,时间复杂度是o((A+B)*轮数)。空间复杂度是o(A+B),因为k轮只和k-1轮有关,可以用滚动数组。


#include <cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;

const int M=105;
const double EPS=1e-7;
double p[M][11000];
int win(int A,int B,int C,int D,double p1)
{
   int i,k;
   for(i=0;i<=A+B;i++) if(i+D>A+B) p[i][1]=1-p1;
   else p[i][1]=0;
   for(k=2;k<=10000;k++)
   for(i=0;i<=A+B;i++)
   {
       p[i][k]=0;
       if(i-C>=0) p[i][k]+=p1*p[i-C][k-1];
       p[i][k]+=(1-p1)*((i+D>A+B)?1:p[i+D][k-1]);
   }
   if(fabs(p[A][k-1]-0.5)<EPS) return 50;
   return int(p[A][k-1]*100);
}


解法二:

我们还是直接写出和轮数无关的状态转移方程,p[i]表示小a最初有i块钱时赢的概率,用矢量P 表示(p[0],p[1],…,p[A+B])。状态转移方程写出来是P=T*P+Q的形式,我们直接解方程求出P,也就得到了p[A]。复杂度是o((A+B)^3),即高斯消去法的复杂度。这个方法在数学上是精确的,即求出的是无限轮的概率,而不是解法一求出的实际是有限轮的概率。


#include <cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstdlib>
using namespace std;

const int M=105;
const double EPS=1e-7;

int win(int A,int B,int C,int D,double p1)
{
   int n;
  double a[M][M],b[M];
     int flag,k,i,j,is;
    double d,t;
    int js[M];
    n=A+B+1;

    for(i=0;i<n;i++)
    {
     for(j=0;j<n;j++) a[i][j]=(i==j)?1.0:0.0;
     b[i]=0;
    }
    for(i=0;i<n;i++)
    {
        if(i>=C) a[i][i-C]-=p1;
        if(i+D<=A+B) a[i][i+D]-=1-p1;
        else b[i]=1-p1;
    }
    flag=1;
    for(k=0;k<=n-2;k++)
      {
          d=0.0;
        for(i=k;i<=n-1;i++)
          for(j=k;j<=n-1;j++)
            {
                t=fabs(a[i][j]);
              if(t>d) //找最大主元
              {
                  d=t;
                  js[k]=j;
                  is=i;
              }
            }
              if(js[k]!=k) //列变换
              for(i=0;i<=n-1;i++)
                  swap(a[i][k],a[i][js[k]]);
              if(is!=k)
              {
                  //行变换
                  for (j=k;j<=n-1;j++)
                      swap(a[is][j],a[k][j]);
                  swap(b[is],b[k]);
              }
        d=a[k][k];
        for(j=n-1;j>=k;j--) //归一化
            a[k][j]/=d;
        b[k]/=d;
        //消元
        for(i=k+1;i<=n-1;i++)
          {
              t=a[i][k];
              for(j=k;j<n;j++)
                a[i][j]-=t*a[k][j];
            b[i]-=t*b[k];
          }
      }
    d=a[n-1][n-1];
    b[n-1]/=d;
    for(i=n-2;i>=0;i--)
      {
          t=0.0;
        for(j=i+1;j<=n-1;j++)
          t+=a[i][j]*b[j];
        b[i]-=t;
      }
    js[n-1]=n-1;
    for(k=n-1;k>=0;k--)
      if(js[k]!=k) swap(b[k],b[js[k]]);
    if(fabs(b[A]-0.5)<EPS) return 50;
    return (int)(b[A]*100);
}

转载自: http://blog.csdn.net/shuyechengying/article/details/9822799

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不会唱歌的老樊

老少爷们,来个赏!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值