题目:
游戏规则:给定三个因素N,K,W。游戏开始的时候有0点,如果发现自己手上的点数不足K点,则随机从1到W的整数中抽取一个,抽到每个整数的概率相同。重复上述步骤,直到手里的点数大于或等于K点。求这时候手上的点数小于等于N的概率。
例:
输入:N = 2,K = 2,W = 5
输出:0.24
说明:开始有0点,不足K(=2)点,于是从[1, 5]中抽取,抽到1或2才可以小于等于N(=2),抽到1和2的概率都是1/5。如果抽到1,则需要再抽一次,且必须抽到1才能小于等于N。因此,结果为1/5 + 1/5*1/5 = 0.24
题目思路:
以N = 21,K = 17,W = 10为例。
我们需要把最后一次抽取和之前的抽取分开考虑。既然最后一次总点数大于等于17,那么最后一次之前可能已经抽取了16,15, ..., 直到7(=17-10)。
(一)最后一次之前
那么最后一次之前已经抽取16的概率是多少呢?这里次数没有限定,可以是:
因此可以用递归的方式来求这个概率。
这里我们可以事先定义一个数组,把已经得到的概率保存下来,这样递归调用的总次数就减少了。
(二)最后一次
再来看最后一次抽取。
所以,这里只要根据N, K, W的大小搞清楚可以抽取哪几点就可以了。
(三)整和在一起
最终概率是:P[之前总共抽取了7] * P[最后一次抽取10] + P[之前总共抽取了8] * P[最后一次抽取9或10] + ...
代码实现:
#include
#include
#include
using namespace std;
int N;
int K;
int W;
vector probVec; // 最后一次之前总共抽取点数的概率
double prob(int total);
int main()
{
while (1)
{
cin >> N >> K >> W;
if (N < K || N > K + W - 1)
{
cout << 0.0 << endl;
continue;
}
// 初始化probVec
probVec.clear();
probVec.push_back(1); // 抽取0的概率设置为1
for (int i = 1; i < K; i++)
probVec.push_back(-1);
double ret = 0;
for (int i = K - 1; i >= K - W && i >= 0; i--) // 最后一次之前已经抽取i点
{
double reti = 1;
int low = K - i; // 最后一次最低可以抽取多少
int high = (i + W > N ? N - i : W); // 最后一次最高可以抽取多少
reti *= (high - low + 1.0) / W; // 最后一次对应的概率
reti *= prob(i); // 最后一次之前对应的概率
ret += reti;
}
cout << fixed << setprecision(5) << ret << endl;
}
return 0;
}
//
// 计算最后一次之前总共抽取total点的概率
//
double prob(int total)
{
if (probVec[total] != -1) // 已经计算好,直接返回即可
return probVec[total];
double ret = 0;
for (int i = 1; i <= W && total - i >= 0; i++) // 第一次抽取i点
{
double reti = 1;
reti *= 1.0 / W; // 选择i的概率
reti *= prob(total - i); // 选择(total-i)的概率
ret += reti;
}
probVec[total] = ret; // 记录在probVec里
return ret;
}