n个骰子求和

本文介绍了一种使用动态规划方法计算掷n个骰子得到不同点数总和的概率的方法。通过构建辅助数组,逐步推导出所有可能的点数总和及其对应概率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

描述

扔 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);
			}
		}
	}

}

递归解法:

/****************************
func:获取n个骰子指定点数和出现的次数
para:n:骰子个数;sum:指定的点数和
return:点数和为sum的排列数
****************************/
class Solution {
public:
    int dicesSumCore(int n,int sum){
        if(n<1 || sum<n || sum>6*n) return 0;
        if(n==1) return 1;
        return (dicesSumCore(n-1,sum-6)+dicesSumCore(n-1,sum-5)
        +dicesSumCore(n-1,sum-4)+dicesSumCore(n-1,sum-3)
        +dicesSumCore(n-1,sum-2)+dicesSumCore(n-1,sum-1));
    }
    /**
     * @param n an integer
     * @return a list of pair<sum, probability>
     */
    vector<pair<int, double>> dicesSum(int n) {
        // Write your code here
        vector<pair<int, double>> res;
        double sum_count=pow(6,n);
        for(int i=n;i<=6*n;i++){
            res.push_back({i,(dicesSumCore(n,i)/sum_count)});
        }
        return res;
    }
};

非递归解法(注意归零操作和long long int操作):

class Solution {
public:
    void dicesSumCore(int n,vector<long long int> &sum){
        sum[0]=0;
        sum[1]=sum[2]=sum[3]=sum[4]=sum[5]=sum[6]=1;
        if(n==1) return;
        for(int i=2;i<=n;i++){
            //把相隔两个单位以上的清零,比如当i=3时,只考虑i=2时产生的2~12,所以必须把sum[0]=sum[1]=0清零
            for (int t = 0; t < i-1; t++) sum[t] = 0; 
            for(int j=6*i;j>=i;j--){
                long long int tmp1=((j-1)>=0?sum[j-1]:0);
                long long int tmp2=((j-2)>=0?sum[j-2]:0);
                long long int tmp3=((j-3)>=0?sum[j-3]:0);
                long long int tmp4=((j-4)>=0?sum[j-4]:0);
                long long int tmp5=((j-5)>=0?sum[j-5]:0);
                long long int tmp6=((j-6)>=0?sum[j-6]:0);
                sum[j]=tmp1+tmp2+tmp3+tmp4+tmp5+tmp6;
            }
        }
    }
    /**
     * @param n an integer
     * @return a list of pair<sum, probability>
     */
    vector<pair<int, double>> dicesSum(int n) {
        // Write your code here
        vector<pair<int, double>> res;
        vector<long long int> sum(6*n+1,0);
        dicesSumCore(n,sum);
        double sum_count=pow(6,n);
        for(int i=n;i<=6*n;i++){
            res.push_back({i,(sum[i]/sum_count)});
        }
        return res;
    }
};

 

### 多项分布的求和及其计算方法 多项分布在统计学中是一种离散型的概率分布,它是二项分布的一种推广形式。当实验的结果有多个类别而非仅两个时(如掷骰子),可以使用多项分布描述这些类别的概率特性。 #### 1. 多项分布的基础定义 假设一次试验可能产生的结果属于 \(k\) 类别之一,每种结果发生的概率分别为 \(\{p_1, p_2, ..., p_k\}\),其中满足条件: \[ \sum_{i=1}^{k} p_i = 1 \] 对于独立重复 \(n\) 次这样的试验,设随机变量 \(X_i\) 表示第 \(i\) 种结果出现的次数,则向量 \((X_1, X_2, ..., X_k)\) 遵循参数为 \(n\) 和 \(\{p_1, p_2, ..., p_k\}\) 的多项分布[^1]。 #### 2. 多项分布的概率质量函数 (PMF) 多项分布的概率质量函数可写为: \[ P(X_1=x_1, X_2=x_2,...,X_k=x_k | n, p_1,p_2,...,p_k)=\frac{n!}{x_1!x_2!...x_k!}p_1^{x_1}p_2^{x_2}...p_k^{x_k}, \] 其中约束条件为: \[ x_1+x_2+...+x_k=n,\quad x_i\geq0.\] 此公式用于计算特定组合下各分类频数的具体概率值。 #### 3. 总体求和的意义 在某些场景下,研究者关心的是某一组分类合计的发生频率或者期望值。例如,在市场调研数据中分析不同年龄段人群的选择偏好时,可能会希望知道某几个年龄层的人群总数占总体的比例。此时就需要对部分或全部分类进行汇总处理。 #### 4. 数学表达式的转换 如果要求数个指定分类 (\(C=\{c_j| j \in J\subset K\}\)) 发生总次数\(S_C\) 的分布情况,则可通过如下方式构建新的简化模型: 令新随机变量 \(Y=S_C=\sum_{j\in C}X_j\), 那么根据线性性质可知其均值与方差分别为: \[ E[Y]=E[\sum_{j\in C}X_j ]=\sum_{j\in C}np_j ,\] \[ Var(Y)=Var(\sum_{j\in C}X_j )=\sum_{j\in C} np_j(1-p_j). \] 注意这里忽略了交叉项因为各个维度间相互独立[^2]。 #### 5. 实际应用中的数值积分技术 尽管上述推导提供了理论上精确解答路径,但在高维空间或多状态情形下直接解析求解变得极其困难甚至不可能完成。这时便引入诸如蒙特卡洛模拟法或是利用复化辛普森法则等数值逼近手段辅助解决此类难题[^4]。 下面展示一段基于Python实现简单版本的复化辛普森公式的代码片段作为示范用途: ```python def simpson(f,a,b,n): h=(b-a)/n s=f(a)+f(b) for i in range(1,int(n/2)): xi=a+(2*i)*h s+=4*f(xi) for i in range(1,int(n/2)-1): xi=a+(2*i+1)*h s+=2*f(xi) return s*h/3. ``` 通过调整输入参数`a`, `b`, 及分割数目`n`即可灵活适应不同的具体应用场景需求。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值