[HNOI2015]亚瑟王

题目描述

小 K 不慎被 LL 邪教洗脑了,洗脑程度深到他甚至想要从亚瑟王邪教中脱坑。他决定,在脱坑之前,最后再来打一盘亚瑟王。既然是最后一战,就一定要打得漂亮。众所周知,亚瑟王是一个看脸的游戏,技能的发动都是看概率的。

作为一个非洲人,同时作为一个前 OIer,小 K 自然是希望最大化造成伤害的期望值。但他已经多年没写过代码,连 Spaly都敲不对了,因此,希望你能帮帮小 K,让他感受一下当欧洲人是怎样的体验。

本题中我们将考虑游戏的一个简化版模型。 玩家有一套卡牌,共 n张。游戏时,玩家将 n 张卡牌排列成某种顺序,排列后将卡牌按从前往后依次编号为 1 ~ n。本题中,顺序已经确定,即为输入的顺序。每张卡牌都有一个技能。第 i 张卡牌的技能发动概率为 pi,如果成功发动,则会对敌方造成di点伤害。也只有通过发动技能,卡牌才能对敌方造成伤害。基于现实因素以及小K非洲血统的考虑,pi不会为 0,也不会为 1,即 0 < pi < 1。 一局游戏一共有 r 轮。在每一轮中,系统将从第一张卡牌开始,按照顺序依次考虑每张卡牌。在一轮中,对于依次考虑的每一张卡牌:

1如果这张卡牌在这一局游戏中已经发动过技能,则

1.1 如果这张卡牌不是最后一张,则跳过之(考虑下一张卡牌); 否则(是最后一张),结束这一轮游戏。

2否则(这张卡牌在这一局游戏中没有发动过技能),设这张卡牌为第 i 张

2.1将其以 pi的概率发动技能。

2.2如果技能发动,则对敌方造成 di点伤害,并结束这一轮。

2.3如果这张卡牌已经是最后一张(即 i 等于n),则结束这一轮;否则,考虑下一张卡牌。

请帮助小 K 求出这一套卡牌在一局游戏中能造成的伤害的期望值。

输入输出格式

输入格式:

输入文件的第一行包含一个整数 T,代表测试数据组数。 接下来一共 T 组数据。 每组数据的第一行包含两个用空格分开的整数 n和r,分别代表卡牌的张数和游戏的轮数。 接下来 n行,每行包含一个实数和一个整数,由空格隔开,描述一张卡牌。第i 行的两个数为 pi和 di,分别代表第 i 张卡牌技能发动的概率(实数)和技能发动造成的伤害(整数)。保证 pi最多包含 4位小数,且为一个合法的概率。

输出格式:

对于每组数据,输出一行,包含一个实数,为这套卡牌在这一局游戏中造成的伤害的期望值。对于每一行输出,只有当你的输出和标准答案的相对误差不超过10^-8时——即|a-o|/a<=10-8时(其中a是标准答案,o是输出),你的输出才会被判为正确。建议输出10 位小数。

输入输出样例

输入样例#1: 复制
1
3 2
0.5000 2
0.3000 3
0.9000 1
输出样例#1: 复制
3.2660250000

说明

一共有 13 种可能的情况:

  1. 第一轮中,第 1张卡牌发动技能;第二轮中,第 2张卡牌发动技能;

概率为 0.15,伤害为5。

  1. 第一轮中,第 1张卡牌发动技能;第二轮中,第 3张卡牌发动技能;

概率为 0.315,伤害为3。

  1. 第一轮中,第 1张卡牌发动技能;第二轮不发动技能;

概率为 0.035,伤害为2。

  1. 第一轮中,第 2张卡牌发动技能;第二轮中,第 1张卡牌发动技能;

概率为 0.075,伤害为5。

  1. 第一轮中,第 2张卡牌发动技能;第二轮中,第 3张卡牌发动技能;

概率为 0.0675,伤害为4。

  1. 第一轮中,第 2张卡牌发动技能;第二轮不发动技能;

概率为 0.0075,伤害为3。

  1. 第一轮中,第 3张卡牌发动技能;第二轮中,第 1张卡牌发动技能;

概率为 0.1575,伤害为3。

  1. 第一轮中,第 3张卡牌发动技能;第二轮中,第 2张卡牌发动技能;

概率为 0.04725,伤害为4。

  1. 第一轮中,第 3张卡牌发动技能;第二轮不发动技能;

概率为 0.11025,伤害为1。

  1. 第一轮不发动技能;第二轮中,第 1张卡牌发动技能;

概率为 0.0175,伤害为2。

  1. 第一轮不发动技能;第二轮中,第 2张卡牌发动技能;

概率为 0.00525,伤害为3。

  1. 第一轮不发动技能;第二轮中,第 3张卡牌发动技能;

概率为 0.011025,伤害为1。

  1. 第一轮不发动技能;第二轮亦不发动技能;

概率为 0.001225,伤害为0。

造成伤害的期望值为概率与对应伤害乘积之和,为 3.266025。

对于所有测试数据, 1 <= T <= 444, 1 <= n <= 220, 0 <= r <= 132, 0 < pi < 1, 0 <= di <= 1000。

除非备注中有特殊说明,数据中 pi与di均为随机生成。

请注意可能存在的实数精度问题,并采取适当措施。

【spj】

概率dp显然,然而我不知道怎么定义 转移

于是一开始写了个深搜 骗了10分,其他点都T了,果然还是太菜了

在洛谷上看了一篇非常棒的题解,终于开始有点理解了。

思路:求总期望,根据期望的线性性质,就是求每张牌能发出的概率×每张牌能打出的攻击之和。设每张牌打出的概率为dp[i],由于每轮出牌都是从前向后枚举,易知考虑到此张牌的次数就是总次数减去他前面发出的张数(因为出出一张牌之后就直接进入下一轮,这一轮将不再考虑这之后的牌),由于若没出第一张牌将在每一轮考虑,所以

首先考虑第一张卡的fpfpfp,也就是fp[0]fp[0]fp[0],应该为

fp[0]=1−(1−p[i])r\Large fp[0]=1-(1-p[i])^{r}fp[0]=1(1p[i])r

这个很容易理解,因为(1−p[i])r(1-p[i])^r(1p[i])r就是这张卡从头到尾始终憋着不出的概率

那么对于后面的fpfpfp应该怎么求呢

有个条件很烦人,就是在每一轮中,出了一张卡的时候立即结束该轮

那么下面就轮到dp上场啦!

f[i][j]f[i][j]f[i][j]表示在所有的rrr轮中,前iii张卡中一共出了jjj张的概率,那么就可以用O(n)O(n)O(n)的时间算出$fpi$

枚举前i−1i-1i1轮选了jjj张牌,那么有jjj轮不会考虑到第iii张牌,也就是有r−jr-jrj轮会考虑到第iii张牌

那么根据上面的分析,1−(1−p[i])r−j1-(1-p[i])^{r-j}1(1p[i])rj就是在r−jr-jrj轮中使用过第iii张牌的概率,式子:

fp[i]=∑j=0rf[i−1][j]⋅(1−(1−p[i])r−j)(i>0)\Large fp[i]=\sum\limits_{j=0}^{r}f[i-1][j]\cdot(1-(1-p[i])^{r-j})(i>0) fp[i]=j=0rf[i1][j](1(1p[i])rj)(i>0)

接下来只要写出f[i][j]f[i][j]f[i][j]的转移方程就好了,分两种情况讨论

第一种,f[i][j]f[i][j]f[i][j]从f[i−1][j]f[i-1][j]f[i1][j]转移过来,即第iii张牌最终没有选,始终不选第iii张牌的概率是(1−p[i])r−j(1-p[i])^{r-j}(1p[i])rj

f[i][j]+=f[i−1][j]⋅(1−p[i])r−j(i>0)\Large f[i][j]+=f[i-1][j]\cdot(1-p[i])^{r-j}(i>0) f[i][j]+=f[i1][j](1p[i])rj(i>0)

第二种,当j>0j>0j>0时,f[i][j]f[i][j]f[i][j]可以从f[i−1][j−1]f[i-1][j-1]f[i1][j1]转移过来,表示最终选择了第iii张牌

这时候,有j−1j-1j1轮没有考虑到第iii张牌,所以考虑到第iii张牌的轮数是r−j+1r-j+1rj+1,最终选择的概率为1−(1−p[i])r−j+11-(1-p[i])^{r-j+1}1(1p[i])rj+1

f[i][j]+=f[i−1][j−1]⋅(1−(1−p[i])r−j+1)(i>0,j>0)\Large f[i][j]+=f[i-1][j-1]\cdot(1-(1-p[i])^{r-j+1})(i>0,j>0) f[i][j]+=f[i1][j1](1(1p[i])rj+1)(i>0,j>0)

然后就没了,总时间复杂度O(Tnr)O(Tnr)O(Tnr)

#include<iostream>
#include<cstring>
#include<cstdio>
#define dd double
#define N 310 
using namespace std;
int t,n,r;
dd p[N],h[N],dp[N],f[N][N],ans;//dp[i]表示出出第i张牌的概率,f[i][j]表示在r轮中前i张牌取了j张的概率
dd pow(dd a,int x)
{
    dd ans=1;
    while(x)
    {
    if(x&1)ans=ans*a;
    a=a*a;
    x>>=1;
    }
    return ans;
}
int main()
{
    scanf("%d",&t);
    while(t--)
    {
    scanf("%d%d",&n,&r);
    memset(dp,0,sizeof(dp));
    memset(f,0,sizeof(f));ans=0;
    for(int i=1;i<=n;i++)scanf("%lf%lf",&p[i],&h[i]);
    dp[1]=1.0-pow((1-p[1]),r);//第一个被打出的概率就是1-r轮后都不打出的概率(因为每一轮都从第一张牌开始决策)
    ans=dp[1]*h[1];
    f[0][0]=1;
    for(int i=1;i<=n;i++)
        for(int j=0;j<=min(i,r);j++)
        {
        f[i][j]+=f[i-1][j]*(pow(1-p[i],r-j));//考虑到第i张牌的r-j轮都不打出
        if(j-1>=0)f[i][j]+=f[i-1][j-1]*(1-pow(1-p[i],r-j+1));//逆向思考
        }
    for(int i=2;i<=n;i++)
    {
        for(int k=0;k<=r;k++)dp[i]+=f[i-1][k]*(1-(pow(1-p[i],r-k)));
        ans+=dp[i]*h[i];
    }
    printf("%.10lf\n",ans);
    }
    return 0;
}
原文地址:https://www.cnblogs.com/lxykk/p/7726989.html