骰子游戏 牛客链接
题目描述:
小易参加了一个骰子游戏,这个游戏需要同时投掷n个骰子,每个骰子都是一个印有数字1~6的均匀正方体。
小易同时投掷出这n个骰子,如果这n个骰子向上面的数字之和大于等于x,小易就会获得游戏奖励。
小易想让你帮他算算他获得奖励的概率有多大。输入描述:
输入包括两个正整数n和x(1 ≤ n < 25, 1 ≤ x < 150),分别表示骰子的个数和可以获得奖励的最小数字和。输出描述:
输出小易可以获得奖励的概率。 如果概率为1,输出1,如果概率为0,输出0,其他以最简分数(x/y)的形式输出。
解题思路:
可能出现的值的范围为:n~6*n
1、回溯法
回溯法枚举n个骰子(6面)的全排列,然后计算每一次排列所有值的和,并统计该和的出现的次数,除以6^n(全排列的全部可能性),即为概率。
2. dp
假设f(m,s)表示投第m个骰子时,点数之和s出现的次数,投第m个骰子时的点数之和只与投第m-1个骰子时有关。
递归方程:f(m,s)=f(m-1,s-1)+f(m-1,s-2)+f(m-1,s-3)+f(m-1,s-4)+f(m-1,s-5)+f(m-1,s-6),表示本轮点数和为s出现次数等于上一轮点数和为s-1,s-2,s-3,s-4,s-5,s-6出现的次数之和。
初始条件:第一轮的f(1),f(2),f(3),f(4),f(5),f(6)均等于1.
需要求的是:f(n , n)、 f(n, n+1)....f(n, 6*n)
下面的代码: 是用了空间压缩的dp,并且一定要注意 dp数组应该是long long,因为全部排列的6^n 很大,用int会溢出vector<vector<long long>> dp(2,vector<long long>(6*n,0));
代码:
#include <iostream>
#include <cmath>
#include <vector>
using namespace std;
int main(){
int n, x;
cin>>n>>x;
vector<long long> dp(6*n+1);
for (int i= 1; i <= 6; i++)
dp[i] = 1;
for (int i = 2; i<=n; i++){
for(int j = 6*i; j>= i; j--){
dp[j] = 0;
for (int k = 1; k <= 6 && k <= j-i+1; k++){
// 上一轮的 点数范围在 【i-1,6*i(i-1)】
// 所以如果 j-k 小于 i-1 则越界了
dp[j] += dp[j-k];
}
}
}
long long ok_sum = 0;
long long all_sum = pow(6, n);
x = max(x, n);
for (int i = x; i<= 6*n; i++){
ok_sum += dp[i];
}
if (ok_sum == 0)
cout << 0 << endl;
else if (ok_sum == all_sum)
cout << 1 << endl;
else{
while (all_sum % 2 == 0 && ok_sum % 2 == 0){
all_sum /= 2;
ok_sum /= 2;
}
while (all_sum % 3 == 0 && ok_sum % 3 == 0){
all_sum /= 3;
ok_sum /= 3;
}
cout << ok_sum <<'/' << all_sum << endl;
}
}
另外再附上,求最大公约数的代码:
long gcd(long a, long b) {
long t = a % b;
while (t) {
a = b; b = t; t = a % b;
}
return b;
}