/*
n个骰子的点数:
把n个骰子仍在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。
骰子一共6个面。n个骰子的和的最小值为n,最大值为6n。n个骰子的所有点数的排列数为6^n。我们需用统计
每一个点数出现的次数,然后把每一个点数出现的次数除以6^n,就能求出每个点数出现的概率。
递归解法:
如何统计每个点数出现的次数?
可以把n个骰子分成两堆,第一堆只有1个,第二堆有n-1个。单独的那一个可能出现1到6的点数+剩下的n-1个骰子
的点数和
接下来把n-1个骰子再分成1和n-2,重复上述过程。
。。。
3个骰子分成1和2
2个筛子分成1和1
f(n) = f(n-1) + f(n-2) + f(n-3) + f(n-4) + f(n-5) + f(n-6)
基于循环求骰子点数:
用两个数组存储骰子点数的每一个总数出现的次数。在一次循环中,第一个数组中的第n个数字表示骰子和为n
出现的次数。在下一个循环中,加上一个新的筛子,此时和为n的骰子出现的次数应该等于上一次循环中
骰子点数和为n-1、n-2、n-3、n-4、n-5、n-6的次数的总和
牛的一比啊,的确是这样的。你想好了,因为后面会出现0~6,6种可能,会在原有基础上面累加形成。
所以把另一个数组的第n个数字设为前一个数组的第n-1,n-2,...,n-6之和。
定义一个长度为6n-n+1的数组,和为s的点数出现的次数保存到数组第s-n个元素中
输入:
输入有多组数据。
每组数据一行,包含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
关键:
用划分又他妈改变了数组原来的下标,这样想要求的数字不知道是谁了。除非用结构体,把最大的几个搞出来
只能用稳定的,用一个set来做,只能这样,还可以
用冒泡排序来做,因为最多81个数字,然后,冒泡一轮确定一个最大的数,同时输出它的下标
*/
/*
关键:未能做出
1 用两个数组存储骰子点数的每一个总数出现的次数。在一次循环中,第一个数组中的第n个数字表示骰子和为n
出现的次数。在下一个循环中,加上一个新的筛子,此时和为n的骰子出现的次数应该等于上一次循环中
骰子点数和为n-1、n-2、n-3、n-4、n-5、n-6的次数的总和
2 int* pDice[2];//函无指有,这里是指针数组,pDice是一个含有两个元素的数组,不过这两个元素都是指针类型
3 for(int i = 1 ; i <= iDotNum ; i++)//设定第一个骰子的初始值
{
pDice[iFlag][i] = 1;
}
4 for(int k = 2 ; k <= iDiceNum ; k++)//iDiceNum个骰子,因此构造大循环,由于第一次已经赋值结束了,所以这里从第二次开始
{
for(int j = 0 ; j < k; j++)//对于骰子取不到的值,直接赋值为0
{
pDice[1-iFlag][j] = 0;
}
for(int i = k ; i <= iDotNum*k ; i++)//对k到iDotNum*k之间的所有元素进行赋值
{
pDice[1-iFlag][i] = 0;
for(int j = 1 ; j <= i && j <= iDotNum ; j++)//更新数组元素的值,这里的j类似与n-1,n-2,..,n-iDotNum中的变量
{
pDice[1-iFlag][i] += pDice[iFlag][i-j];
}
*/
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
#include <algorithm>
using namespace std;
const int MAXSIZE = 80 + 1;//最多10个骰子,最大点数为8,所以数组的总元素个数至多为8*10+1
typedef struct Dice
{
Dice():_iNum(0),_iTimes(0){}
Dice(int iNum,int iTimes):_iNum(iNum),_iTimes(iTimes){}
bool operator < (const Dice& other) const
{
if(_iTimes != other._iTimes)
{
return _iTimes < other._iTimes;
}
else
{
return _iNum > other._iNum;
}
}
void set(int iNum,int iTimes)
{
_iNum = iNum;
_iTimes = iTimes;
}
//Dice& operator = (const Dice& other)
//{
// if(this != &other)
// {
// _iNum = other._iNum;
// _iTimes = other._iTimes;
// }
// return *this;
//}
//bool operator <= (const Dice& other)
//{
// return _iTimes <= other._iTimes;
//}
int _iNum;//哪个点数
int _iTimes;//点数出现的次数
}Dice;
Dice diceArr[MAXSIZE];
void swap(Dice* pNum1,Dice* pNum2)
{
Dice iTemp = *pNum1;
*pNum1 = *pNum2;
*pNum2 = iTemp;
}
int randomInRange(int min,int max)
{
return (rand() % (max - min + 1) + min);
}
//template<typename T>
//直接用快排,然后取倒数3个么。从后向前,把小于的放到后面,大于的放到前面,不就可以了吗,不行,把这些数的下标变了,而我们是需要下标的,坑爹了
//int partition(Dice* pArr,int low,int high)//并且选取最大的3个数,那么应该自定义排序为
//{
// int iIndex = randomInRange(low,high);
// swap(&pArr[low],&pArr[iIndex]);
// Dice iAxis = pArr[low];
// while(low < high)
// {
// while(low < high && pArr[high] <= iAxis)
// {
// high--;
// }
// pArr[low] = pArr[high];
// while(low < high && iAxis <= pArr[low])
// {
// low++;
// }
// pArr[high] = pArr[low];
// }
// pArr[low] = iAxis;
// return low;
//}
//
//void maxKNum(Dice* pArr,int low,int high,int k)
//{
// int iIndex = partition(pArr,low,high);
// int iRes = k - 1 + low;
// while(iIndex != iRes)//因为下标从0开始,当下标为k-1时,此时实际上有k个数已经排好了,这里数组无序之后,不能直接用k-1,必须加上low
// {
// if(iIndex < iRes)//如果下标小于k-1,说明应该将low变为iIndex+1,并且继续划分
// {
// low = iIndex + 1;
// }
// else
// {
// high = iIndex - 1;
// }
// iIndex = partition(pArr,low,high);
// }
//}
//思路:
//刚开始声明两个数组,并对数组初始化为0,设置第一个数组1到6为1,开始进行筛子个数个循环(从第二层开始循环),
//尤其注意,比如两个骰子,筛子点数之和的下标为0,1都取不到,至少为2
void diceProbability(int iDiceNum,int iDotNum)
{
if(iDiceNum < 1)
{
return;
}
int* pDice[2];//函无指有,这里是指针数组,pDice是一个含有两个元素的数组,不过这两个元素都是指针类型
int iDice1Arr[MAXSIZE];
int iDice2Arr[MAXSIZE];
pDice[0] = iDice1Arr;
pDice[1] = iDice2Arr;
int iFlag = 0;//靠,这里果然是得用一个二级指针来做,否则选取的两个数组就写死了
memset(iDice1Arr,0,sizeof(iDice1Arr));
memset(iDice2Arr,0,sizeof(iDice2Arr));
for(int i = 1 ; i <= iDotNum ; i++)//设定第一个骰子的初始值
{
pDice[iFlag][i] = 1;
}
for(int k = 2 ; k <= iDiceNum ; k++)//iDiceNum个骰子,因此构造大循环,由于第一次已经赋值结束了,所以这里从第二次开始
{
for(int j = 0 ; j < k; j++)//对于骰子取不到的值,直接赋值为0
{
pDice[1-iFlag][j] = 0;
}
for(int i = k ; i <= iDotNum*k ; i++)//对k到iDotNum*k之间的所有元素进行赋值
{
pDice[1-iFlag][i] = 0;
for(int j = 1 ; j <= i && j <= iDotNum ; j++)//更新数组元素的值,这里的j类似与n-1,n-2,..,n-iDotNum中的变量
{
pDice[1-iFlag][i] += pDice[iFlag][i-j];
}
}
iFlag = 1 - iFlag;
}
//将出现次数的数组转化为结构体来做
memset(diceArr,NULL,sizeof(diceArr));
for(int i = iDiceNum ; i <= iDotNum*iDiceNum ; i++)
{
diceArr[i].set(i,pDice[iFlag][i]);
}
sort(diceArr + iDiceNum,diceArr + iDotNum*iDiceNum + 1);
//接下来选取次数最多的三个,题目转化为了在一个一维数组乱序数组中,寻找值最大的3个数,输出其下标,变成了寻找max3那道题目,可以采用partition来做,不需要用快排
//maxKNum(diceArr,iDiceNum,iDotNum*iDiceNum,3);//上标是iDiceNum,下标是iDotNum*iDiceNum,因为partition的下标是取到的,类似与快排
double total = pow(double(iDotNum),iDiceNum);
for(int i = iDotNum*iDiceNum ; i > iDotNum*iDiceNum - 3; i--)
{
printf("%d %.2f\n",diceArr[i]._iNum,1.0*diceArr[i]._iTimes/total);
}
}
void process()
{
int n,m;
while(EOF != scanf("%d %d",&n,&m))
{
if(n == 0)
{
break;
}
if(n < 0 || n > 10 || m < 6 || m > 8)
{
continue;
}
diceProbability(n,m);
}
}
int main(int argc,char* argv[])
{
process();
getchar();
return 0;
}