n个骰子的点数
n個(gè)骰子的點(diǎn)數(shù)
文章目錄
- n個(gè)骰子的點(diǎn)數(shù)
- 一、題目描述
- 二、分析
- 方法一:基于遞歸,效率低
- 代碼一:
- 方法二:基于循環(huán),性能好
- 代碼二:
- 方法三:動(dòng)規(guī)
- 代碼三:
一、題目描述
把n個(gè)骰子仍在地上,所有的骰子朝上的一面的點(diǎn)數(shù)之和為s,輸入n,打印出s所有可能的值出現(xiàn)的概率。
二、分析
- 骰子一共有6個(gè)面,每個(gè)面都有一個(gè)點(diǎn)數(shù),對(duì)應(yīng)的是1-6的數(shù)字,所以 n個(gè)骰子的最小點(diǎn)數(shù)之和是n,最大點(diǎn)數(shù)之和是6n
- 根據(jù)排列組合的知識(shí),n個(gè)骰子所有點(diǎn)數(shù)的排列一共有6^n
- 這道題求和為s的所有可能的值出現(xiàn)的概率,所以我們需要先統(tǒng)計(jì)每個(gè)點(diǎn)數(shù)出現(xiàn)的次數(shù),然后把每個(gè)點(diǎn)數(shù)出現(xiàn)的次數(shù)再分別除以6^n次方,即可求每個(gè)點(diǎn)數(shù)出現(xiàn)的概率
方法一:基于遞歸,效率低
- 現(xiàn)在我們考慮如何統(tǒng)計(jì)每一個(gè)點(diǎn)數(shù)出現(xiàn)的次數(shù)。
- 想要求n個(gè)骰子的點(diǎn)數(shù)和,我們可以 把n個(gè)骰子分為兩堆,一堆只有一個(gè)骰子,另一堆有n - 1個(gè)骰子
- 只有一個(gè)骰子的那一堆可能出現(xiàn)點(diǎn)數(shù)為1~6的點(diǎn)數(shù),我們就需要分別計(jì)算從1到6的每一種情況和另一堆剩下n - 1個(gè)骰子來(lái)計(jì)算點(diǎn)數(shù)和
- 接下來(lái)還是 把n - 1個(gè)骰子分為兩堆,一堆只有一個(gè)骰子,另一堆有n - 2個(gè)骰子
- 我們把上一輪單獨(dú)的骰子和這一輪單獨(dú)的骰子的點(diǎn)數(shù)相加,然后再和剩下的n - 2個(gè)骰子來(lái)計(jì)算點(diǎn)數(shù)和
- 到這里,我們就發(fā)現(xiàn)這是一個(gè)遞歸的問題:由小問題逐漸求得大問題;那么base case 就是當(dāng)骰子個(gè)數(shù)為1的時(shí)候
- 那么我們可以定義一個(gè)長(zhǎng)度為6n - n + 1(6n是所有骰子最大的和,- n是因?yàn)椴豢赡艹霈F(xiàn)比骰子個(gè)數(shù)n還小的總和,所以節(jié)省n個(gè)空間)的數(shù)組用來(lái)保存相應(yīng)總和點(diǎn)數(shù)的出現(xiàn)的次數(shù),和為s的點(diǎn)數(shù)出現(xiàn)的次數(shù)保存在數(shù)組的第s - n(從0號(hào)位置開始)個(gè)元素里
代碼一:
#include <iostream> #include <math.h> #include <vector> #include <algorithm> using namespace std;//為了可拓展性,把骰子的最大面值定義一個(gè)變量 int g_maxValue = 6;//original:代表骰子的總個(gè)數(shù) //index:代表當(dāng)前是那個(gè)骰子 //curSum:當(dāng)前總和 //pProbability:保存結(jié)果每個(gè)總和s出現(xiàn)的次數(shù) void Probability(int original,int index,int curSum,vector<int>& pProbability) {//等于0,就代表把n個(gè)骰子不斷的分為【1,n - 1】、【1,n - 2】直到【2,1】,//即只剩下一個(gè)骰子,直接++即可,代表這是和為curSum - original一種if(index == 0){//curSum為總和//original代表骰子的個(gè)數(shù),即把和為curSum的出現(xiàn)次數(shù)+1pProbability[curSum - original] += 1;return;}//枚舉一個(gè)骰子的面值情況,遞歸進(jìn)行for(int i = 1;i <= g_maxValue;i++)Probability(original,index - 1,curSum + i,pProbability); }//n代表骰子的個(gè)數(shù) void PrintProbability(int n) {if(n < 1)return;//最大的和int maxSum = n * g_maxValue;//保存結(jié)果vector<int> pProbability(n * 6 - n + 1,0);int curSum = 0;Probability(n,n,curSum,pProbability);//總的出現(xiàn)所有次數(shù)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; }方法二:基于循環(huán),性能好
- 換一種思路,我們用兩個(gè)數(shù)組來(lái)存儲(chǔ)骰子點(diǎn)數(shù)的每一個(gè)總數(shù)出現(xiàn)的次數(shù)
- 在本次循環(huán)中,第一個(gè)數(shù)組的第n個(gè)數(shù)字表示骰子和為n出現(xiàn)的次數(shù)
- 在下一次循化中,我們?cè)谏弦淮窝h(huán)的基礎(chǔ)上加一個(gè)新的骰子,此時(shí)和為n的出現(xiàn)的次數(shù)就因該等于和為n - 1,n - 2,n - 3,n - 4,n - 5,n - 6的次數(shù)總和
- 所以我們把另一個(gè)數(shù)組的第n個(gè)數(shù)字設(shè)為前一個(gè)數(shù)組的第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;//定義兩個(gè)數(shù)組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;}//用來(lái)控制兩個(gè)數(shù)組的交替使用int flag = 0;//第一個(gè)骰子只可能出現(xiàn)1,2,3,4,5,6的情況,所以在對(duì)應(yīng)的位置值為1for(int i = 1;i <= g_maxValue;i++)pProbability[flag][i] = 1;//從第二個(gè)骰子開始判斷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];}//交換兩個(gè)數(shù)組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; }方法三:動(dòng)規(guī)
上面兩種都是《劍指offer》里面的解法,都比較難懂,現(xiàn)在將一種動(dòng)規(guī)的簡(jiǎn)單些
- 首先該問題具備DP的兩個(gè)特征:最優(yōu)子結(jié)構(gòu)性質(zhì)和子問題的重疊性。
- 具體的表現(xiàn)在:(1)n個(gè)骰子的點(diǎn)數(shù)依賴于n-1個(gè)骰子的點(diǎn)數(shù),相當(dāng)于在n-1個(gè)骰子點(diǎn)數(shù)的基礎(chǔ)上再進(jìn)行投擲。(2)求父問題的同時(shí),需要多次利用子問題。
- 由此定義狀態(tài)轉(zhuǎn)移方程為dp(𝑛,𝑘)表示𝑛個(gè)骰子點(diǎn)數(shù)和為𝑘時(shí)出現(xiàn)的次數(shù)
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++)//初始狀態(tài):base casearr[1][i] = 1;for(int i = 2; i <= n; i++)//狀態(tài)轉(zhuǎn)移方程{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]);}}//輸出結(jié)果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; }總結(jié)
- 上一篇: B-、B树详解及模拟实现
- 下一篇: 字节--异或