問題:
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);
}