求n个骰子的点数

来源:互联网 发布:淘宝fgo充值便宜 编辑:程序博客网 时间:2024/05/17 23:27

 百度2010年某个部门(不记得是哪个了)的实习生笔试题第一题就是这种题,只是一点小小的改动而已。

 

原题依然来源于网络中某位大侠的BLOG,感谢提供素材:)

 

写这篇blog是因为原文中提到的方法和原文评论中的方法相关比较大,评论中的方法用到了DP,效率好很多。后来仔细想想,这种实现方法用“表格法”来解释更恰当,至底向上填写表格,最终得到结果。另外,这种至底向上的填表法,当前表格的值只与下一层表格的值有关,所以实现中并没有分配所有表格空间,只用了两行,一行保存上一次的结果,另一行保存现在正在计算的值,这里是可以节省很多空间的。这种方法我记得在《算法导论》的“动态规划”一章有详述。

 

 

题目:把n个骰子扔在地上,所有骰子朝上一面的点数之和为S。输入n,打印出S的所有可能的值出现的概率。

 

先分析思路,再看实现。

 

首先解决前提性的问题:一个骰子的点数只可能是[1,6],所以S的值的取值范围是[n,6n],这里当然只考虑整数。

 

思路一:统计各个S值出现的次数,然后

 

      各个S值出现的概率 = 各个S值出现的次数 / n个骰子所有点数的排列数

 

 

其中,n个骰子所有点数的排列数等于6n,而各个S值出现的次数就需要建立一个数组来进行统计。这时,问题就变成怎样来求各个S出现的次数了。方法我直接引用原文的文字如下:

 

 

==========================  以下文字引用自原文 ============================

 

分析:玩过麻将的都知道,骰子一共6个面,每个面上都有一个点数,对应的数字是1 6之间的一个数字。所以,n个骰子的点数和的最小值为n,最大值为6n。因此,一个直观的思路就是定义一个长度为6n-n的数组,和为S的点数出现的次数保存到数组第S-n个元素里。另外,我们还知道n个骰子的所有点数的排列数6^n。一旦我们统计出每一点数出现的次数之后,因此只要把每一点数出现的次数除以6^n,就得到了对应的概率。

该思路的关键就是统计每一点数出现的次数。要求出n个骰子的点数和,我们可以先把n个骰子分为两堆:第一堆只有一个,另一个有n-1个。单独的那一个有可能出现从16的点数。我们需要计算从16的每一种点数和剩下的n-1个骰子来计算点数和。接下来把剩下的n-1个骰子还是分成两堆,第一堆只有一个,第二堆有n-2个。我们把上一轮那个单独骰子的点数和这一轮单独骰子的点数相加,再和剩下的n-2个骰子来计算点数和。分析到这里,我们不难发现,这是一种递归的思路。递归结束的条件就是最后只剩下一个骰子了。

 

==========================  以上文字引用自原文 ============================

 

思路一明晰了,代码应该不难写,同样引用原文的代码:

 

[c-sharp] view plaincopy
  1. int g_maxValue = 6;  
  2.   
  3.    
  4.   
  5. void PrintSumProbabilityOfDices(int number)  
  6.   
  7. {  
  8.   
  9.     if(number < 1)  
  10.   
  11.         return;  
  12.   
  13.    
  14.   
  15.     int maxSum = number * g_maxValue;  
  16.   
  17.     int* pProbabilities = new int[maxSum - number + 1];  
  18.   
  19.     for(int i = number; i <= maxSum; ++i)  
  20.   
  21.         pProbabilities[i - number] = 0;  
  22.   
  23.    
  24.   
  25.     SumProbabilityOfDices(number, pProbabilities);  
  26.   
  27.    
  28.   
  29.     int total = pow((float)g_maxValue, number);  
  30.   
  31.     for(int i = number; i <= maxSum; ++i)  
  32.   
  33.     {  
  34.   
  35.         float ratio = (float)pProbabilities[i - number] / total;  
  36.   
  37.         printf("%d: %f/n", i, ratio);  
  38.   
  39.     }  
  40.   
  41. }  
  42.   
  43.    
  44.   
  45. void SumProbabilityOfDices(int number, int* pProbabilities)  
  46.   
  47. {  
  48.   
  49.     for(int i = 1; i <= g_maxValue; ++i)  
  50.   
  51.         SumProbabilityOfDices(number, number, i, 0, pProbabilities);  
  52.   
  53. }  
  54.   
  55.    
  56.   
  57. void SumProbabilityOfDices(int original, int current, int value, int tempSum, int* pProbabilities)  
  58.   
  59. {  
  60.   
  61.     if(current == 1)  
  62.   
  63.     {  
  64.   
  65.         int sum = value + tempSum;  
  66.   
  67.         pProbabilities[sum - original]++;  
  68.   
  69.     }  
  70.   
  71.     else  
  72.   
  73.     {  
  74.   
  75.         for(int i = 1; i <= g_maxValue; ++i)  
  76.   
  77.         {  
  78.   
  79.             int sum = value + tempSum;  
  80.   
  81.             SumProbabilityOfDices(original, current - 1, i, sum, pProbabilities);  
  82.   
  83.         }  
  84.   
  85.     }  
  86.   
  87. }  

思路二:利用基本的概率论知识,而不需要统计所有可能的S出现的次数。为了方便,这里先讨论某个S出现的概率,设为P(S),则有

    P(S) = P(S1) + P(S2) + ... + P(Sk)

S1,S2...Sk表示和为S的各种骰子组合。另外,

 

    P(Si) = P(a1) + P(a2) + ... + P(an)

其中,P(ai)表示第i个骰子出现值为ai的概率。

很简单的,就是一个概率论的东西,很基本的。不需要统计所有可能的S出现的次数,而直接计算和为S的各种可能的骰子组合的概率,然后把所有组合的概率相加,就得到了和为S的概率了。

先把代码贴出来吧,没上机测试,细节上有什么问题不太清楚,关键是主程序应该是没问题的。代码来源于原文的评论中。

[c-sharp] view plaincopy
  1. #include <iostream>  
  2. int main(){  
  3.     const int N = 20; double a[2][6*N+1] ={(0.0),{0.0}};  
  4.     for (int i = 1;i<=6;i++) a[1][i] = 1.0/6; int flag = 1;  
  5.     for (int k = 2;k<=N;k++) {  
  6.         for (int i = 0;i<=6*N;i++) {  
  7.             a[1-flag][i] = 0; int j = 1;  
  8.             while (j<=i && j<=6)   a[1-flag][i] += a[flag][i-j++]/6;  
  9.         }  
  10.         flag = 1-flag;  
  11.     }  
  12.     for (int i = 1;i<=6*N;i++)  std::cout<<i<<": "<<a[flag][i] <<std::endl;  
  13. }   

如果看懂了代码就不用看下面的了,下文只是代码的一个注释,以免以后老了脑子不好使看不懂……

这种方法是DP中的表格法,用至底向上填表的方式,把结果求出来。用表格法,一行代表一个骰子,列表示各个S值,所以一共有6*N列。本来是要用N行的,可是这里只用了一个二维的数组,因为现在计算的值只与前一次计算的值相关,所以其中一行保存上一次计算的结果,另一行保存正在计算的结果,这样可以节省大量的空间。

代码中的N是指有几个骰子,或者说是掷了几次骰子。第一个for循环表示第一个骰子的情况,然后第二个for循环中的k表示第k个骰子。当到了第k个骰子时,内层的for循环开始对和个S的值进行分析,i表示的就是各个不同的S。在这个循环里,考虑第k个骰子的6种不同取值(j表示的就是骰子的点数),然后在while循环里把所有可能的得到和为S的组合的概率进行相加。flag的作用很简单,就是在二维数组里对当时值和已经计算得到的值进行区别,他只出现在数组的行号处。

 

 

画个图的话,就容易理解了。有时间再上图吧。

 

最后,附上原文地址:http://zhedahht.blog.163.com/blog/static/254111742009101524946359/

0 0