问题描述
有 1, 2,..., n ,n 个数字,其中有且仅有一个数字是中奖的,这个数字是等概率随机生成的。
Alice 和 Bob 进行一个游戏:
两人轮流猜一个 1 到 n 的数字,Alice 先猜。
每完成一次猜测,主持会大声说出刚刚的数字是猜小了还是猜大了或者猜中了,若猜中则猜的人赢下游戏。
假设两人都是聪明的理智的,而且都知道对方是聪明的,求 Alice 获胜的概率,请保留 5 位小数输出答案。
输入格式
一个正整数 n
- 100% 的数据满足 n ≤ 10^3
输出格式
保留 5 位小数输出答案。
输入样例
2
输出样例
0.50000
解题思路:
动态规划思路
-
定义状态:
- 设
memo[i]
表示在有i
个数字的情况下,Alice获胜的概率。
- 设
-
初始条件:
- 当只有一个数字时,Alice直接获胜,即
memo[1] = 1.0
。 - 当有两个数字时,Alice有50%的概率获胜,即
memo[2] = 0.5
。
- 当只有一个数字时,Alice直接获胜,即
-
状态转移方程:
- 对于每个
i
(从3到n),Alice可以选择猜任何一个数字j
(从1到i)。 - 如果Alice猜
j
,她获胜的概率是memo[j-1]
(如果猜小了)和memo[i-j]
(如果猜大了)的加权平均值。 - 具体来说,Alice获胜的概率可以表示为:
[
memo[i] = \max_{1 \leq j \leq i} \left( \frac{1}{i} + \frac{j-1}{i} \cdot (1 - memo[j-1]) + \frac{i-j}{i} \cdot (1 - memo[i-j]) \right)
]
- 对于每个
-
计算顺序:
- 从
i = 3
开始,逐步计算到i = n
。
- 从
-
最终结果:
memo[n]
即为Alice在有n
个数字的情况下获胜的概率。
代码实现:
1.首先是特判的部分:在1和2的时候答案是固定的
if (n == 1)
return "1.00000";
if (n == 2)
return "0.50000";
2. 初始化:创建vector数组来做dp,同时把1和2的值放入数组
vector<double> memo(n + 1, 0.0);
memo[1] = 1.0;
memo[2] = 0.5;
3.状态更新:
整个表达式 1.0 / i + ((double)(j - 1) / i * (1 - memo[j - 1])) + ((double)(i - j) / i * (1 - memo[i - j]))
表示 Alice 选择猜数字 j
时,她获胜的总概率。这个概率是以下三部分的和:
- 猜中数字
j
的概率。 - 猜的数字
j
比中奖数字小的情况下,Alice 在剩下的j - 1
个数字中获胜的概率。 - 猜的数字
j
比中奖数字大的情况下,Alice 在剩下的i - j
个数字中获胜的概率。
对于直接猜中的:
1.0 / i是猜中一个数字的基础概率;
对于猜到小的:((double)(j - 1) / i * (1 - memo[j - 1]))
(j - 1) / i
是猜的数字 j
比中奖数字小的概率
(1 - memo[j - 1])
是如果猜的数字 j
比中奖数字小,Alice 在剩下的 j - 1
个数字中获胜的概率。
猜到大的: ((double)(i - j) / i * (1 - memo[i - j]))
(i - j) / i
是猜的数字 j
比中奖数字大的概率。
(1 - memo[i - j])
是如果猜的数字 j
比中奖数字大,Alice 在剩下的 i - j
个数字中获胜的概率。
for (int i = 3; i <= n; i++) {
for (int j = 1; j <= i; j++) {
memo[i] = std::max(memo[i], 1.0 / i + ((double)(j - 1) / i * (1 - memo[j - 1]))
+ ((double)(i - j) / i * (1 - memo[i - j])));
}
}
最终代码:
#include <iostream>
#include <string>
#include <vector>
#include <iomanip>
#include <algorithm>
std::string solution(int n) {
// Please write your code here
if (n == 1)
return "1.00000";
if (n == 2)
return "0.50000";
std::vector<double> memo(n + 1, 0.0);
memo[1] = 1.0;
memo[2] = 0.5;
for (int i = 3; i <= n; i++) {
for (int j = 1; j <= i; j++) {
memo[i] = std::max(memo[i], 1.0 / i + ((double)(j - 1) / i * (1 - memo[j - 1]))
+ ((double)(i - j) / i * (1 - memo[i - j])));
}
}
std::ostringstream oss;
oss << std::fixed << std::setprecision(5) << memo[n];
return oss.str();
}
int main() {
// You can add more test cases here
std::cout << (solution(2) == "0.50000") << std::endl;
std::cout << (solution(931) == "0.50054") << std::endl;
std::cout << (solution(924) == "0.50000") << std::endl;
std::cout << (solution(545) == "0.50092") << std::endl;
return 0;
}
运行结果: