九度OJ-题目1360:乐透之猜数游戏

题目链接地址:

九度OJ-题目1360:乐透之猜数游戏


题目描述:
六一儿童节到了,YZ买了很多丰厚的礼品,准备奖励给JOBDU里辛劳的员工。为了增添一点趣味性,他还准备了一些不同类型的骰子,打算以掷骰子猜数字的方式发放奖品。例如,有的骰子有6个点数(点数分别为1~6),有的骰子有7个(点数分别为1~7),还有一些是8个点数(点数分别为1~8) 。他每次从中拿出n个同一类型的骰子(假设它们都是拥有m个点数并且出现概率相同)投掷,然后让员工在纸上按优先级(从高到低)的顺序写下3个数上交,表示他们认为这些骰子最有可能的点数之和是多少。第一个数就猜对的人,是一等奖;第二个数才猜对的人是二等奖;如果三个数都不是正确答案,别灰心!YZ还准备了很多棒棒糖。ZL很聪明,他想了想,打算把概率(以保留两位小数的概率计)最高的三个数找出来,如果有概率相同,则选择其中点数和最小的那个数。你觉得ZL会依次写下哪三个数?

输入:
输入有多组数据。
每组数据一行,包含2个整数n(0<=n<=10),m(6<=m<=8),n表示YZ拿出的骰子数,m表示骰子拥有的点数。如果n=0,则结束输入。

输出:
对应每组数据,输出ZL最可能依次写下的点数,以及其对应的概率值。概率值按4舍5入要求保留2位小数。每组数据之间空一行,注意:最后一组数据末尾无空行。

样例输入:
1  6
4  6
3  7
0


样例输出:
1  0.17
2  0.17
3  0.17

13  0.11
14  0.11
15  0.11

12  0.11
10  0.10
11  0.10


解题思路:

这道题的题意很简单,就是找出n个m点骰子的点数和的出现概率最高的三个数。
假设拿出n个骰子数,每个骰子的点数是m,则可以得出以下结论:
(1)n个m点骰子的点数和sum的取值范围是n <= sum <= n * m;
(2)n个m点骰子一共有m ^ n种点数和(m ^ n表示m的n次方);
(3)每种点数和sum出现的概率是 sum / (m ^ n)。
现在的关键问题是如何求出n个m点骰子的每种点数和的出现次数。
想了很久也没有想出好的办法,只好去看作者的博客 程序员面试题精选100题(43)-n个骰子的点数[算法]  了。
看了几遍也没能理解作者的思路,后来就动手举了几个例子,模拟了作者的解题过程,这才彻底明白了作者的解题思路,囧。。。 所以当我们看不懂某个算法时,可以尝试将一个具体的测试用例代入到算法中,然后执行一遍算法,也许就能弄明白算法的原理了,╮(╯▽╰)╭

下面介绍一下计算n个m点骰子的每种点数和的出现次数的过程:
设拿出的骰子数是n,每个骰子的点数是m,dicePointTimes[i][j]表示 i 个骰子点数和是 j 的出现次数;请脑补这样的画面:现在将一堆骰子分为两部分,一部分是已经计算出点数和出现次数的骰子堆A(A堆初始状态下只有1个骰子),剩余部分是还没有计算出点数和出现次数的骰子堆B,每次都从B堆中拿出1个骰子丢到A堆中,然后重新计算A堆的骰子点数和出现次数。那么当B堆中的骰子全部丢到A堆后,就能算出n个m点骰子的每种点数和的出现次数了。
假设现在前i–1个骰子的点数和出现次数已经计算完毕,而后n – (i - 1)个骰子的点数和还没有被计算。现在将第 i 个骰子加入到前i-1个骰子中,求 i 个骰子点数和是 j (i <= j < = i * m)的出现次数的过程如下:
因为增加一个骰子后,点数和可以增加的范围是1 ~ m。
因此可以由以下m种情况可以推导出 i 个骰子点数和为 j:
(1)将前i-1个骰子点数和为j-1加上第 i 个骰子的点数1后,可以得到 i 个骰子点数和为 j;
(2)将前i-1个骰子点数和为j-2加上第 i 个骰子的点数2后,可以得到 i 个骰子点数和为 j;
          ……
(m)将前i-1个骰子点数和为j-m加上第 i 个骰子的点数m后,可以得到 i 个骰子点数和为 j。
因此可以推出 i 个骰子点数和是 j 的出现次数等于前n-1个骰子点数和为 t (j-m <= t <= j-1)的出现次数之和,
即dicePointTimes[i][j] = dicePointTimes[i - 1][j - m] + ... + dicePointTimes[i - 1][j - 1]
当只有1个骰子时,每个点数和的出现次数都是1,即dicePointTimes[1][j] = 1(1 <= j <= m)。
如果觉得上述的过程生涩难懂,可以自己举个实例,手动模拟算法的执行过程就能很好地理解这个算法了。
此题还有一点需要注意:题目要求先算出各个点数和出现的概率,再将所得到的概率四舍五入并保留两位小数后,才把概率(以保留两位小数的概率计)最高的三个数找出来。

AC代码如下:

#include<stdio.h>
#include<math.h>
#define N 11                  // N表示骰子数的最大值
#define M 9                   // M表示单个骰子点数的最大值
#define MAX (M * N)           // MAX表示N个骰子点数和的最大值
 
double dicePointTimes[N][MAX];    //保存每种骰子点数出现的次数,dicePointTimes[i][j]表示i个骰子点数和为j的次数
double dicePointProbability[MAX]; //每个骰子点数出现的概率
bool isDicePointSelected[MAX];    //标记排序过程中某个骰子点数是否已经被选择过
 
/**
* 初始化每个点数之和的出现次数
* @param n  表示骰子的数目
* @param m  表示每个骰子的点数
* @return void
*/
void initDicePointTimes(int n,int m)
{
  int i,j;
  for(i = 1;i <= n;i++)
  {
      for(j = 1;j <= n * m;j++)
      {
          dicePointTimes[i][j] = 0;
      }
  }
}
 
/**
* 统计n个m点的骰子的每种点数之和所对应的出现次数
* @param n  表示骰子的数目
* @param m  表示每个骰子的点数
* @return void
*/
void getDicePointTimes(int n,int m)
{
  int i,j,t;
  initDicePointTimes(n,m);
  // 第1个骰子各个点数出现的次数都是1
  for(i = 1; i <= m;i++)
  {
      dicePointTimes[1][i] = 1;
  }
  // 依次添加第i(2 <= i <= n)个后,所得到的骰子点数和的出现次数
  for(i = 2; i <= n;i++)
  {
      for(j = i;j <= i * m;j++)  // j表示i个骰子点数和,j的范围是[i,i * m]
      {
          // dicePointTimes[i][j] = dicePointTimes[i - 1][j - m] + ... + dicePointTimes[i - 1][j - 1]
          for(t = j - m;t <= j - 1;t++)
          {
              if(t >= 1)  // 防止数组越界,因为骰子点数最小是1,所以t >= 1
                dicePointTimes[i][j] += dicePointTimes[i - 1][t];
          }
      }
  }
}
 
/**
* 统计每个骰子点数和出现的概率
* @param n  表示骰子的数目
* @param m  表示每个骰子的点数
* @return void
*/
void getDicePointProbability(int n,int m)
{
   int i;
   double totalDicePointTimes;         // 表示n个点数为m的骰子所有点数和出现的总次数
   totalDicePointTimes = pow(m,n);     // n个点数为m的骰子所有点数之和出现的总次数为 m ^ n
   for(i = n; i <= n * m;i++)          // n个点数为m的骰子的点数之和范围是[n,n * m]
   {
      dicePointProbability[i] = dicePointTimes[n][i] / totalDicePointTimes;
      dicePointProbability[i] = ((int)(100 * dicePointProbability[i] + 0.5)) / 100.0; //根据题目要求要对概率值按4舍5入要求保留2位小数
   }
}
 
/**
* 取出3个出现概率最大的点数和
* @param n  表示骰子的数目
* @param m  表示每个骰子的点数
* @return void
*/
void guessNumber(int n,int m)
{
  int i,j;
  double maxDicePointProbability;   // 用于某次选择过程中保存最大的骰子点数之和出现的最大概率
  int dicePoint;                    // 每次选择出来的骰子点数
  getDicePointProbability(n,m);
  for(i = n;i <= n * m;i++)
  {
      isDicePointSelected[i] = false;
  }
  // 选出3个出现概率最大的骰子点数之和
  for(i = 1;i <= 3;i++)
  {
    maxDicePointProbability = -1;
    for(j = n;j <= n * m;j++)
    {
      if(false == isDicePointSelected[j] && dicePointProbability[j] > maxDicePointProbability)
      {
          maxDicePointProbability = dicePointProbability[j];
          dicePoint = j;
      }
    }
    isDicePointSelected[dicePoint] = true;
    printf("%d %.2lf\n",dicePoint,dicePointProbability[dicePoint]);
  }
}
 
int main()
{
    int n,m;
    int nullLine = 0;         // 用于控制是否在数据末尾输出空行
    while(EOF != scanf("%d",&n) && 0 != n)
    {
        scanf("%d",&m);
        getDicePointTimes(n,m);
        getDicePointProbability(n,m);
        if(0 != nullLine)
           printf("\n");
        guessNumber(n,m);
        nullLine++;
    }
    return 0;
}
 
/**************************************************************
    Problem: 1360
    User: blueshell
    Language: C++
    Result: Accepted
    Time:10 ms
    Memory:1116 kb
****************************************************************/


猜你喜欢

转载自blog.csdn.net/pengyan0812/article/details/46473525