n个骰子的点数
n個骰子的點數
文章目錄
- n個骰子的點數
- 一、題目描述
- 二、分析
- 方法一:基于遞歸,效率低
- 代碼一:
- 方法二:基于循環,性能好
- 代碼二:
- 方法三:動規
- 代碼三:
一、題目描述
把n個骰子仍在地上,所有的骰子朝上的一面的點數之和為s,輸入n,打印出s所有可能的值出現的概率。
二、分析
- 骰子一共有6個面,每個面都有一個點數,對應的是1-6的數字,所以 n個骰子的最小點數之和是n,最大點數之和是6n
- 根據排列組合的知識,n個骰子所有點數的排列一共有6^n
- 這道題求和為s的所有可能的值出現的概率,所以我們需要先統計每個點數出現的次數,然后把每個點數出現的次數再分別除以6^n次方,即可求每個點數出現的概率
方法一:基于遞歸,效率低
- 現在我們考慮如何統計每一個點數出現的次數。
- 想要求n個骰子的點數和,我們可以 把n個骰子分為兩堆,一堆只有一個骰子,另一堆有n - 1個骰子
- 只有一個骰子的那一堆可能出現點數為1~6的點數,我們就需要分別計算從1到6的每一種情況和另一堆剩下n - 1個骰子來計算點數和
- 接下來還是 把n - 1個骰子分為兩堆,一堆只有一個骰子,另一堆有n - 2個骰子
- 我們把上一輪單獨的骰子和這一輪單獨的骰子的點數相加,然后再和剩下的n - 2個骰子來計算點數和
- 到這里,我們就發現這是一個遞歸的問題:由小問題逐漸求得大問題;那么base case 就是當骰子個數為1的時候
- 那么我們可以定義一個長度為6n - n + 1(6n是所有骰子最大的和,- n是因為不可能出現比骰子個數n還小的總和,所以節省n個空間)的數組用來保存相應總和點數的出現的次數,和為s的點數出現的次數保存在數組的第s - n(從0號位置開始)個元素里
代碼一:
#include <iostream> #include <math.h> #include <vector> #include <algorithm> using namespace std;//為了可拓展性,把骰子的最大面值定義一個變量 int g_maxValue = 6;//original:代表骰子的總個數 //index:代表當前是那個骰子 //curSum:當前總和 //pProbability:保存結果每個總和s出現的次數 void Probability(int original,int index,int curSum,vector<int>& pProbability) {//等于0,就代表把n個骰子不斷的分為【1,n - 1】、【1,n - 2】直到【2,1】,//即只剩下一個骰子,直接++即可,代表這是和為curSum - original一種if(index == 0){//curSum為總和//original代表骰子的個數,即把和為curSum的出現次數+1pProbability[curSum - original] += 1;return;}//枚舉一個骰子的面值情況,遞歸進行for(int i = 1;i <= g_maxValue;i++)Probability(original,index - 1,curSum + i,pProbability); }//n代表骰子的個數 void PrintProbability(int n) {if(n < 1)return;//最大的和int maxSum = n * g_maxValue;//保存結果vector<int> pProbability(n * 6 - n + 1,0);int curSum = 0;Probability(n,n,curSum,pProbability);//總的出現所有次數int total = pow((double)g_maxValue,n);for(int i = n;i <= maxSum;i++){double ratio = (double)pProbability[i - n] / total;cout<<i<<":"<<pProbability[i - n]<<" "<<ratio<<" "<<endl;} }int main() {int number;while(cin>>number){PrintProbability(number);} return 0; }方法二:基于循環,性能好
- 換一種思路,我們用兩個數組來存儲骰子點數的每一個總數出現的次數
- 在本次循環中,第一個數組的第n個數字表示骰子和為n出現的次數
- 在下一次循化中,我們在上一次循環的基礎上加一個新的骰子,此時和為n的出現的次數就因該等于和為n - 1,n - 2,n - 3,n - 4,n - 5,n - 6的次數總和
- 所以我們把另一個數組的第n個數字設為前一個數組的第n - 1,n - 2,n - 3,n - 4,n - 5,n - 6之和。
代碼二:
#include <iostream> #include <math.h> using namespace std;int g_maxValue = 6; void PrintProbability(int n) {if(n < 1)return;//定義兩個數組int* pProbability[2];pProbability[0] = new int[g_maxValue * n + 1];pProbability[1] = new int[g_maxValue * n + 1];//初始化for(int i = 0;i <= g_maxValue * n;i++){pProbability[0][i]=0;pProbability[1][i]=0;}//用來控制兩個數組的交替使用int flag = 0;//第一個骰子只可能出現1,2,3,4,5,6的情況,所以在對應的位置值為1for(int i = 1;i <= g_maxValue;i++)pProbability[flag][i] = 1;//從第二個骰子開始判斷for(int k = 2;k <= n;k++){for(int i = 0;i < k;i++)pProbability[1 - flag][i] = 0;for(int i = k;i <= g_maxValue * k;i++){pProbability[1 - flag][i] = 0;for(int j = 1;j <= i && j <= g_maxValue;j++)pProbability[1 - flag][i] += pProbability[flag][i - j];}//交換兩個數組flag=1 - flag;}int total = pow((double)g_maxValue,n);for(int i = 0;i <= g_maxValue * n;i++){double ratio=(double)pProbability[flag][i] / total;cout<<i<<":"<<pProbability[flag][i]<<" "<<ratio<<" "<<endl;}delete[] pProbability[0];delete[] pProbability[1]; }int main() {int n;while(cin>>n){PrintProbability(n);}return 0; }方法三:動規
上面兩種都是《劍指offer》里面的解法,都比較難懂,現在將一種動規的簡單些
- 首先該問題具備DP的兩個特征:最優子結構性質和子問題的重疊性。
- 具體的表現在:(1)n個骰子的點數依賴于n-1個骰子的點數,相當于在n-1個骰子點數的基礎上再進行投擲。(2)求父問題的同時,需要多次利用子問題。
- 由此定義狀態轉移方程為dp(𝑛,𝑘)表示𝑛個骰子點數和為𝑘時出現的次數
dp(𝑛,𝑘)=dp(𝑛?1,𝑘?1)+dp(𝑛?1,𝑘?2)+dp(𝑛?1,𝑘?3)+dp(𝑛?1,𝑘?4)+dp(𝑛?1,𝑘?5)+dp(𝑛?1,𝑘?6) - base case:第一輪的dp(1),dp(2),dp(3),dp(4),dp(5),dp(6)均等于1.
𝑓(1,1)=𝑓(1,2)=𝑓(1,3)=𝑓(1,4)=𝑓(1,5)=𝑓(1,6)=1
代碼三:
#include<iostream> #define MAX_NUM 100 using namespace std;void FindSum(int n) {if(n <= 0)return;int sum = 0;int arr[n + 1][6 * n + 1];memset(arr,0,sizeof(arr));for(int i = 1; i <= 6; i++)//初始狀態:base casearr[1][i] = 1;for(int i = 2; i <= n; i++)//狀態轉移方程{for(int j = i; j <= 6 * i; j++)//注意j的范圍受i影響{arr[i][j] += (arr[i - 1][j - 1] + arr[i - 1][j - 2] + arr[i - 1][j - 3] + arr[i - 1][j - 4] + + arr[i - 1][j - 5] +arr[i - 1][j - 6]);}}//輸出結果for(int i = n; i <= 6 * n; i++){sum += arr[n][i];}for(int i = n; i <= 6 * n; i++){cout<<i<<":"<<arr[n][i]<<" "<<(arr[n][i] * 1.0 / sum)<<endl;} }int main() {int n;while(cin>>n){FindSum(n);}return 0; }總結
- 上一篇: B-、B树详解及模拟实现
- 下一篇: 字节--异或