n個色子的點數

問題:

n個色子,每個色子m面,每一面的值分別爲m-1。你將n個色子同時拋,落地後將所有朝上面的數字加起來,記爲sum。給定一個數字x,如果sum>x,則你贏。給定n,m,x,求你贏的概率。


分析:

何海濤的《劍指offer》中有類似的題目,但這裏問的更Gerneral。

要求的是概率,所以可以從兩個方面入手:種類數 & 概率。

PS:認爲每個面的值爲 0,1,...,m-1。


一、從種類數入手:

總的種類數爲m^n,只需求出其中滿足sum>x的個數K,即可得到概率: K / (m^n)。

方法I:暴力枚舉

枚舉每個色子的點數,判斷是否大於x。

此方法存在重複計算,複雜度爲指數級。

方法II:動態規劃

用f[i,k]表示前i個色子擲出的點數和爲k的種數。那麼:

f[i+1,k] = Σ f[i, k-j] , j = 0,1,...,m-1。

算法可以使用滾動數組記錄:開兩個長度爲 n*(m-1)+1的數組即可。

還可以仿照01揹包的實現:逆序求和。那樣的話,就只需要開一個數組了。

代碼:時間複雜度O(n * nm),空間複雜度O(nm)。

double getProperity(int n, int m, int x){
	int sum = (m - 1) * n;
	if(sum <= x) return 0;
	vector<int> count(sum+1, 0);
	fill_n(count.begin(), m, 1);//只有一個色子時
	int maxSum = m - 1;
	for(int dicei = 1; dicei < n; ++dicei){
		maxSum += m-1;
		int counti = accumulate(count.begin() + maxSum + 1 - m, 
			count.begin() + maxSum + 1, 0);//統計前m個的和
		for(int sumi = maxSum; sumi > 0; --sumi){//逆序遍歷,只用一個數組即可
			int tmp = count[sumi];
			count[sumi] = counti;
			counti -= tmp;
			counti += sumi - m < 0 ? 0 : count[sumi - m];
		}
	}
	return accumulate(count.begin() + x + 1, count.end(), 0.0) / pow((double)m, (double)n);
}
此代碼不使用滾動數組、不用遍歷m個點數,在時間和空間上都比《劍指offer》給出的代碼實現要更優


二、直接從概率入手:

仍然是動態規劃思想:

用f[i,k]表示前i個色子擲出的點數和爲k的概率。那麼:

f[i+1,k] = Σ f[i, k-j] / m, j = 0,1,...,m-1。

代碼:與上面的實現類似,時間複雜度O(n * nm),空間複雜度O(nm)。

double getProperity2(int n, int m, int x){
	int sum = (m - 1) * n;
	if(sum <= x) return 0;
	vector<double> properity(sum+1, 0.0);
	fill_n(properity.begin(), m, 1.0/m);//只有一個色子時
	int maxSum = m - 1;
	for(int dicei = 1; dicei < n; ++dicei){
		maxSum += m-1;
		double properityi = accumulate(properity.begin() + maxSum + 1 - m, 
			properity.begin() + maxSum + 1, 0.0);//統計前m個的和
		for(int sumi = maxSum; sumi >= 0; --sumi){//逆序遍歷,只用一個數組即可
			double tmp = properity[sumi];
			properity[sumi] = properityi / m;
			properityi -= tmp;
			properityi += sumi - m < 0 ? 0 : properity[sumi - m];
		}
	}
	return accumulate(properity.begin() + x + 1, properity.end(), 0.0);
}




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章