问题描述:把n个色子掷地上,所有朝上一面的和为S,求出所有可能的S的概率,分析可知,色子有6个面,其数是1~6,而总共有n个色子,那么其和的范围是n~6n,而总共可能出现的组合情况有6^n种。
/**************方法一**************/
通过每次确定一个色子的数字,确定n个色子出现的数字,然后根据其和,相应的次数+1,从第一个色子开始确定,逐个确定色子的数字,一个色子确定后,要确定下一个色子,这一过程通过递归来进行。而对于某个色子的数字确定,则是通过循环来进行,循环确定一个色子可能出现的几种情况。
#include <iostream>
using namespace std;
const int Biggest_Value = 6;
void ConfirmDicesNums(int* times, int CurDices, int CurSums, int n);
void PrintProbability_Solution1(int n)
{
if (n == 0)
{
return;
}
int NumScale = Biggest_Value*n - n + 1;
//定义用来存储各个和出现次数的数组
int* Probability = new int[NumScale];
memset(Probability, 0, NumScale*sizeof(int));
//第一个色子可能出现的数字开始进入递归
for (int i = 1; i <= Biggest_Value; i++)
{
// 第一个色子编号为1,进入后为下一个色子,编号+1,此时的和
//为第一个色子点数即i
ConfirmDicesNums(Probability, 1 + 1, i, n);
}
for (int i = 0; i < NumScale;i++)
{
cout << i + n << ":" << Probability[i] / pow(Biggest_Value, n) << endl;
}
}
CurDices代表当前色子的编号,而CurSums则代表的是之前确定下的色子的和,当前色子确定的数字为i,加上后为当前确定色子总和,在最后一个色子时候,进入下一个递归,那么CurDices+1则是超出了编号,而CurSums+i为加上了最后一个色子的数字后的结果,此时判断色子是否超出编号,然后对应的和出现次数+1;
void ConfirmDicesNums(int* times, int CurDices, int CurSums, int n)
{
//如果色子编号到n+1,意味着已经确定所有色子的点数
if (CurDices > n)
{
times[CurSums - n]++;
return;
}
//本色子可能情况也有1~Biggest_Value种
//每确定一次,则进入递归确定下一个
for (int i = 1; i <= Biggest_Value; i++)
{
ConfirmDicesNums(times, CurDices + 1, CurSums + i, n);
}
}
/*************************************/
/******************方法二********************/
本方法是通过色子掷出的数字规律来计算每个和的次数:
当只有一个色子的时候,数字范围是1~6,两个的时候为2~12,…,n个色子的时候为n~6n。
对于第k个色子而言,其可能掷出的情况有6种,即1~6。假设掷出第k个色子后,其和为sum,如果要使得k各色子的和为sum的话:
当第k个色子掷出1的时候,前面k-1个色子应该和为sum-1;
当第k个色子掷出2的时候,前面k-1个色子应该和为sum-2;
以此类推当掷出6,则前面k-1个色子,应该掷出sum-6;
也就是说,对于k个色子而言,和为sum的情况有6种,即当k-1个色子和分别为sum-1、sum-2、sum-3、sum-4、sum-5、sum-6的时候,那么k个色子和为sum的次数应该是k-1个色子在和为这几个数字时候的次数相加。
因此,可以定义2个数组来分别存储k-1个色子时候的各个和的次数以及k各色子时候各个和的次数,而k个色子时候各个和的次数是通过k-1个色子时候的几个和的次数相加得到的。
其操作过程为:
一个大循环,以色子数目作为循环条件,从第一个色子算起,
在大循环内再以当前色子数目对应的和的范围作为循环条件,计算每个和出现的次数,而出现次数的计算是通过色子掷出的数字为循环:即1~6,相应在存储k-1个色子各个和出现次数的数组中取出对应的次数,累加得到当前色子数相应和出现的次数。
另外需要注意的是,当新增加一个色子的时候,k个色子所对应的和的取值范围中,并不是每个值都是通过k-1个色子时候的6个值的次数相加得到,因为:
k-1个色子的和的范围是k-1~6(k-1),而k个色子的和的范围是k~6k:
k-1、k、k+1、k+2、k+3、k+4……、6k-7、6k-6;
k、k+1、k+2、k+3、k+4……、6k-7、6k-6、6k-5、6k-4、6k-3、6k-2、6k-1、6k
对于k个色子时候的前几个值,色子掷出数字的循环不应该是1~6,
如:和为k的时候,掷出1,k-1个色子对应和只能是k-1;和为k+1的时候,掷出1、2,k-1个色子对应和只能是k和k-1;
以此类推前5个值其循环都是小于6的,且由上面规律可知:
// 第一个和,循环为k-(k-1)==1
// 第二个和,循环为k+1-(k-1)==2…….
// 可知,循环为:当前待求和-当前色子数-1
// 也就是在k个色子时候,循环求某一和出现次数的时候
// 掷出点数的循环应该同时满足小于6和小于上面这一条件
void PrintProbability_Solution2(int n)
{
if (n == 0)
{
return;
}
//因为要从只有一个色子开始计算,因而和的值也是从1开始算
//的,所以数组长度应为最大的那个数
int SumsScale = Biggest_Value*n;
int* Times1 = new int[SumsScale];
int* Times2 = new int[SumsScale];
memset(Times1, 0, SumsScale*sizeof(int));
memset(Times2, 0, SumsScale*sizeof(int));
//初始化只有一个色子的时候
for (int i = 0; i < Biggest_Value;i++)
{
Times1[i] = 1;
}
//色子个数的循环
for (int i = 2; i <= n;i++)
{
//以和的范围为循环,计算加入第i个色子后各个和的出现次数
for (int sum = i; sum <=Biggest_Value*i;sum++)
{
//对于i个色子时候的某一和,根据i-1个色子时候来计算
for (int j = 1; j <= sum - i + 1 && j <=
Biggest_Value;j++)
{
Times2[sum - 1] += Times1[sum - j - 1];
}
}
//每次得到k个色子下的各个和的次数后,进入下一个循环(增加
//一个色子)前,应该先把存k个色子数据的数组值赋值给
//存k-1个色子数据的数组中,并且将k个色子中的数据归零,
//应为k数组是对k-1数组的累加,如果不清零则产生错误
for (int sum = i; sum <= Biggest_Value*i; sum++)
{
Times1[sum - 1] = Times2[sum - 1];
Times2[sum - 1] = 0; // 之前未清零导致错误。
}
}
for (int i = n; i <= SumsScale;i++)
{
cout << i << ":" <<
Times1[i - 1]/pow(Biggest_Value, n) << endl;
}
delete []Times1;
delete []Times2;
}
// ====================测试代码====================
void Test(int n)
{
printf("Test for %d begins:\n", n);
printf("Test for solution1\n");
PrintProbability_Solution1(n);
cout << "-----------" << endl;
printf("Test for solution2\n");
PrintProbability_Solution2(n);
printf("\n");
}
int main(int argc, char* argv[])
{
Test(1);
Test(2);
Test(3);
Test(4);
Test(5);
Test(11);
Test(0);
return 0;
}