描述
Little Hi is playing a video game. Each time he accomplishes a quest in the game, Little Hi has a chance to get a legendary item.
At the beginning the probability is P%. Each time Little Hi accomplishes a quest without getting a legendary item, the probability will go upQ%. Since the probability is getting higher he will get a legendary item eventually.
After getting a legendary item the probability will be reset to ⌊P/(2I)⌋% (⌊x⌋ represents the largest integer no more than x) where I is the number of legendary items he already has. The probability will also go upQ% each time Little Hi accomplishes a quest until he gets another legendary item.
Now Little Hi wants to know the expected number of quests he has to accomplish to get N legendary items.
Assume P = 50, Q = 75 and N = 2, as the below figure shows the expected number of quests is
2*50%*25% + 3*50%*75%*100% + 3*50%*100%*25% + 4*50%*100%*75%*100% = 3.25
输入
The first line contains three integers P, Q and N.
1 ≤ N ≤ 106, 0 ≤ P ≤ 100, 1 ≤ Q ≤ 100
输出
Output the expected number of quests rounded to 2 decimal places.
提示也隐藏在这张图中,我们仔细观察可以发现红圈中的子树和黄圈中的子树一模一样。
这提示我们,无论第一件传说物品是怎么获得的(完成第一件任务就获得还是完成两件任务后获得),第二件传说物品都是从25%概率开始,与第一件无关。换句话说,每件传说物品的获得都是独立的,就好像掷N枚骰子每枚骰子的点数是相互独立的一样。
我们知道如果X和Y是两个独立的随机变量,那么E(X+Y)=E(X)+E(Y)。于是我们可以分别求出获得第一件、第二件……第N件传说物品的期望任务数,再把它们加起来就是最终答案。
我们知道第i件传说物品起始的概率是⌊P/(2i-1)⌋%,为了描述方便把它记作Pi;任务失败后概率增加Q%。用二叉树表示大概是这样:
由于概率不断增加Q%,我们最多完成100个任务就一定能获得第i件传说物品。所以计算第i件传说物品的期望任务数的复杂度是O(100)。一般我们在计算复杂度时是忽略常数的,不过这里100次计算是一个比较大的常数,为了体现这一点我们姑且把这个复杂度记作O(100)。这样计算N件传说物品的总复杂度就是O(100N)的。这个算法已经相当不错了,大概能得到90/100分。
代码如下:
#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<math.h>
using namespace std;
int main() {
//freopen("input.txt","r",stdin);
int p,q,n;
cin>>p>>q>>n;
double res = 0.0;
int p1;
for(int i = 1; i <= n; i++) {
if(i < 8) p1 = floor(p / pow(2,i-1));
else p1 = 0;
double p2 = 1.0;
for(int j = 1; j <= 100; j++) {//路经长度
res += p1 / 100.0 * p2 * j;
if(p1 == 100) break;
p2 *= (1 - p1 / 100.0);
p1 = p1 + q;
if(p1 > 100) p1 = 100;
}
}
printf("%.2f\n",res);
return 0;
}
如果我们进一步分析,会发现虽然我们计算了N棵二叉树对应的期望任务数。但这N棵二叉树总共最多有101种形态。因为Q不变,Pi的值唯一决定了这棵二叉树长什么样子,而Pi一共有0~100共101种取值。所以我们只需预先求出起始概率分别是100%、99%、98% … 0%时的期望任务数,保存在数组f[]里。计算第i件传说物品时,根据Pi直接把相应的f[Pi]累加即可。
值得一提的是,我们可以用倒推的方法求出f[],而不用每次O(100)重新计算。
f[100] = 1;
for(int i = 99; i >= 0; i--) {
int j = min(i + Q, 100); //计算如果这次任务没获得,下一个任务获得的概率
//i%的概率1次获得,(1-i%)的概率是从j%起始的期望任务数+1
f[i] = i% * 1 + (1-i%) * (f[j] + 1);
}
于是我们得到了一个O(100+N)的算法,比之前的O(100N)好了100倍。
如果我们再进一步分析,我们会发现⌊P/(2i-1)⌋%会很快下降到0。实际上从第8件传说物品开始,起始概率就一定都是0了。也就是说从第8件开始我们可以直接用f[0]*(N-7)算出期望任务数的和。这样我们得到了一个更快的O(100+8)的算法。这个算法对于每组数据只需要常数次计算即可得到答案。
上文中的证明:
假设,其中为路径长度的概率。则有
其中,,所以。
代码如下:
#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<math.h>
using namespace std;
int main() {
//freopen("input.txt","r",stdin);
int p,q,n;
cin>>p>>q>>n;
double f[101] = {0};
f[100] = 1;
for(int i = 99; i >= 0; i--) {
int j = min(i+q,100);
double pi = i / 100.0; //计算如果这次任务没获得,下一个任务获得的概率
//i%的概率1次获得,(1-i%)的概率是从j%起始的期望任务数+1
f[i] = pi + (1 - pi) * (f[j] + 1);
}
double res = 0;
int pi ;
for(int i = 1; i <= n; i++) {
if(i >= 8) pi = 0;
else pi = floor(p / pow(2,i-1));
res += f[pi];
}
printf("%.2f\n",res);
return 0;
}