#1489 : Legendary Items

时间限制: 10000ms
单点时限: 1000ms
内存限制: 256MB

描述

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.

样例输入 50 75 2 样例输出 3.25 解题思路: 参考链接

提示也隐藏在这张图中,我们仔细观察可以发现红圈中的子树和黄圈中的子树一模一样。

这提示我们,无论第一件传说物品是怎么获得的(完成第一件任务就获得还是完成两件任务后获得),第二件传说物品都是从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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值