题目描述
小源有 n n n 个苹果,他每天都会吃掉一些苹果。
但是小源是个没有主见的人,他每天到底要吃多少个苹果取决于当天剩下多少个。当这一天剩下 k k k 个苹果时,他等概率(即 1 k \frac{1}{k} k1 吃掉 1 , 2 , ⋯ , k 1,2,\cdots,k 1,2,⋯,k 个苹果。
现在想要知道,小源吃掉 m m m 个苹果期望下需要多少天。
输入格式
一行两个整数 n , m n,m n,m,表示最初苹果数和目标苹果数。
输出格式
一个数表示期望天数,保留到小数点后两位。
样例输入
3 3
样例输出
1.83
数据范围
对于 30 % 30\% 30% 的数据有
m ≤ 3 m\leq3 m≤3
对于 50 % 50\% 50% 的数据有
n ≤ 10 n\leq10 n≤10
对于 20 % 20\% 20% 的数据有
n = m n=m n=m
对于 100 % 100\% 100% 的数据有
1 ≤ m ≤ n ≤ 20 1\leq m\leq n\leq20 1≤m≤n≤20
函数递归
本题用到的是函数递归,关于递归的思考:
递归函数就是自己给自己挖一个洞,我调用我自己。【例如,下面的大圆圈表示第一次调用函数的函数入口,函数运行后又调用了自己,相当于大圆圈里面还有一个小圆圈,同理小圆圈继续调用自身这个函数,就形成了一个无底洞】。
显然这是不科学的!为了避免无底洞现象,必须设置终止的条件!!!
所以,写递归函数按照下面的语法来!可以避免无底洞现象。
void function(参数)
{
// 写清楚终止条件
if(终止条件)
return;
else
// 根据实际情况情况调用自身这个函数
function(参数);
}
递归函数,实际是一个过程,比如说,模拟一个过程,比如一次走步,一次决定,或者其他……这个过程要一直执行下去,直到某一个过程。
我们举个例子,求下面方程的解:
x
1
+
x
2
+
x
3
+
x
4
+
x
5
=
10
(
x
1
,
x
2
,
x
3
,
x
4
,
x
5
∈
N
)
x_1+x_2+x_3 + x_4 +x_5=10(x_1,x_2,x_3,x_4,x_5 \in N)
x1+x2+x3+x4+x5=10(x1,x2,x3,x4,x5∈N)
就是可以利用一个递归函数来解决的。我们先求
x
1
x_1
x1 的可能值,然后求
x
2
x_2
x2 的可能值,一直求到
x
5
x_5
x5 的值,递归停止,输出结果。
另外,要注意两个函数之间的通讯值,首先:轮数,其次,排除前面几轮数字的和剩下的数值!
#include <iostream>
using namespace std;
int a[6];
// i 反映当前的轮数,每轮函数查找 x_i 的可能结果,remain反映的remain
void findAns(int i,int remain)
{
if (i == 5)
{
a[5] = remain;
for (int q = 1; q <= 5; q++)
{
cout << a[q] << "\t";
}
cout << endl;
}
else
for (int q = 0; q <= remain; q++)
{
a[i] = q;
findAns(i + 1, remain - q);
}
}
int main()
{
findAns(1, 10);
system("pause");
return 0;
}
题目解答
所以,同样的道理,写递归函数首先要明白一轮函数执行下来模拟的过程是什么?这道题递归函数一轮下来模拟的就是一天下来吃的苹果。
首先我定义了一个类,然后开一个关于这个类的 很大的数组,数组里面每一个元素是一种解决方案,每个元素包括两个要点 1-吃完要的天数 2-可能性。
按照概率统计的知识,这个可能性反映的是全局可能性,而我们求期望需要用的是条件概率。
方案编号 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | ⋯ \cdots ⋯ |
---|---|---|---|---|---|---|---|---|---|
吃完用的天数 | |||||||||
全局可能性 | |||||||||
归一化 概率 |
其中,方案是指所以满足题目要求的方案,即为 小源吃掉
m
m
m 个苹果的解决方法
其中,全局可能性是:基于该方案,每天吃相应苹果数的概率的乘积。
问题: 为什么要设置归一化概率?
解答: 众所周知,计算期望用的概率,所有概率的和是1,那么反问一下自己,全局可能性求和一定是1吗?当且仅当
m
=
n
m=n
m=n 满足,其余不是,所以概率要归一化,就是把所有解决方案的全局可能性求和,用 每一个全局可能性 除以 全局可能性求和,得到对应的归一化概率,然后用归一化概率求期望。
问题: 数组要开多大?
解答: 尽可能大,要包括所有的解决方案。测试结果是需要一百万。(少一个数量级就不能通过!)
问题: 怎么设置两位小数点输出?
解答: 方法:cout << fixed << setprecision(2) << result;
补充: 没用过的可以看看下面的总结
- 头文件要加:
#include <iomanip>
cout << fixed << setprecision(2) << result;
是设置小数点后2位的cout << setprecision(2) << result;
是设置有效位数2位的- C++的流输出
cout
默认是cout << setprecision(6) ;
有效数字6位。 - 注意,一旦使用了这个
setprecision
,后续的所有输出都会生效!除非手动代码设置恢复默认
问题: 有AC代码吗?
解答: 如下:
#include <iostream>
#include <cmath>
#include <iomanip>
using namespace std;
int m, n;
double allpossible = 0;
double result = 0;
class possible_result
{
public:
int day;
double possiblility;
};
possible_result temp[1000000]; // 气死我了要开一百万呜呜呜
int cnt = 0;
void eat(int remain,int day,double p)
{
if (remain == n - m)
{
// cout << day - 1 << "|" << p << endl;
temp[cnt].day = day - 1;
temp[cnt].possiblility = p;
allpossible = allpossible + p;
cnt++;
return;
}
for (int i = 1; i <= remain && i <= m; i++)
{
eat(remain - i, day + 1, p / remain);
}
}
int main()
{
cin >> n >> m;
eat(n, 1, 1);
// getresult(n, 1, 1);
for (int j = 0; j < cnt; j++)
{
result = result + temp[j].day * temp[j].possiblility / allpossible;
}
cout << fixed << setprecision(2) << result;
system("pause");
return 0;
}