n个骰子求和

描述

扔 n 个骰子,向上面的数字之和为 S。给定 Given n,请列出所有可能的 S 值及其相应的概率。

样例

给定 n = 1,返回 [ [1, 0.17], [2, 0.17], [3, 0.17], [4, 0.17], [5, 0.17], [6, 0.17]]


       这是剑指offer的一道题,主要思想为动态规划,由于投n个骰子出现的点数为n~6n,一共为6n-n+1=5n+1个数,所以我们需要申请一个长为5n+1的辅助数组a。数字的下标[i]表示出现的和(0表示n个骰子出现的最小的和,即n),下标对应的值a[i]表示对应的和出现的次数。

       从最简单的开始,假设只有一个骰子,需要长度为6的辅助数组,每个骰子出现的次数为1,于是最后的数组a的长度为6,对应的值为[1,1,1,1,1,1]。假如有两个骰子,我们把骰子的和分为前n-1个骰子的和和第n个骰子的值的相加和,前n-1即2-1=1,即前1个的骰子的和的数组为[1,1,1,1,1,1],由于两个骰子的和的范围为2~12,以和为10为例,10可以分为前n-1组的值+第n个骰子的值,则:

       10=1+9=2+8=3+7=......=9+1

       由于第二个骰子出现的值为1~6,所以10只可能由以下的值组成4+6=5+5=6+4=7+3=8+2=9+1,所以和为10出现的次数等于和为4,5,6,7,8,9的次数相加而得,即f(n)=f(n-1)+f(n-2)+f(n-3)+f(n-4)+f(n-5)+f(n-6),所以这就是递归规律,这里需要注意,我们需要一个新的临时数组tmp来保存生成的值,最后再把tmp的值重新赋值给a。所以程序的思想如下,如果n=1,则通过递推公式构造数组a的0~5(下标)的数,如果n=2,则先构造0~5,再通过0~5构造0~10的数(因为2个骰子出现的和为2~12,归一到下标为0即0~10),往后以此类推。最后需要注意的一点是,由于在递归过程中数组的值(出现的次数)是指数次的增加,所以辅助数组需定义为long long,否则如果定义为int则会溢出(int最大为2147483647),以下为代码(c++,在lintcode上已ac)

//最后的答案返回格式为vector<pair<int, double>>
vector<pair<int, double>> dicesSum(int n) {
	vector<pair<int, double>> result;
	//辅助数组a
	long long *array = new long long[5 * n + 1];
	memset(array, 0, sizeof(long long)*(5 * n + 1));

	dicesSumCore(n, array, result);
	delete[] array;
	return result;
}
//构造递推数组
void calculate(long long* array, int i, int n) {
	//临时标量tmp,作用上文已说过
	long long tmp[10001];
	memset(tmp, 0, sizeof(long long)*(10001));
	//sum表示第i项~第(i-6)项的和
	long long sum = 0;
	for (int j = 0; j <= 5 * (i + 1); j++) {
		//前6项一直累加
		if (j <= 5) {
			sum += (array)[j];
			tmp[j] = sum;
		}
		else {
			//后面的需要减去第i-6项的值
			sum += (array)[j] - (array)[j - 6];
			tmp[j] = sum;
		}
	}
	//最后把tmp的值赋给a
	for (int j = 0; j <= 5 * (i + 1); j++) {
		array[j] = tmp[j];
	}
}
void dicesSumCore(int n, long long* &array, vector<pair<int, double>> & result) {
	//初始化前6个值为1
	for (int i = 0; i < 6; i++) {
		array[i] = 1;
	}
	//如果n==1,则初始化后直接计算并赋值到result中返回
	if (n == 1) {
		for (int i = 0; i < 6; i++) {
			pair<int, double> zy;
			zy.first = i + 1;
			zy.second = array[i] / pow(6, n);
			result.push_back(zy);
		}
		return;
	}
	//i表示第几次递推,每次递推都构造一个n-6n的数组,注意i的下标从0开始,所以i=1表示n=2的第二次构造,构造的数组下标为0-5n(包含5n)
	for (int i = 1; i < n; i++) {
		//构造第i+1次递推数组
		calculate(array, i, n);
		//最后一次经过calculate构造后赋值到result中
		if (i == n - 1) {
			for (int i = 0; i <= 5 * n; i++) {
				pair<int, double> zy;
				zy.first = i + n;
				zy.second = array[i] / pow(6, n);
				result.push_back(zy);
			}
		}
	}

}


猜你喜欢

转载自blog.csdn.net/qq_26410101/article/details/80298295
今日推荐