[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 位小数。
输入输出样例
说明
一共有 13 种可能的情况:
- 第一轮中,第 1张卡牌发动技能;第二轮中,第 2张卡牌发动技能;
概率为 0.15,伤害为5。
- 第一轮中,第 1张卡牌发动技能;第二轮中,第 3张卡牌发动技能;
概率为 0.315,伤害为3。
- 第一轮中,第 1张卡牌发动技能;第二轮不发动技能;
概率为 0.035,伤害为2。
- 第一轮中,第 2张卡牌发动技能;第二轮中,第 1张卡牌发动技能;
概率为 0.075,伤害为5。
- 第一轮中,第 2张卡牌发动技能;第二轮中,第 3张卡牌发动技能;
概率为 0.0675,伤害为4。
- 第一轮中,第 2张卡牌发动技能;第二轮不发动技能;
概率为 0.0075,伤害为3。
- 第一轮中,第 3张卡牌发动技能;第二轮中,第 1张卡牌发动技能;
概率为 0.1575,伤害为3。
- 第一轮中,第 3张卡牌发动技能;第二轮中,第 2张卡牌发动技能;
概率为 0.04725,伤害为4。
- 第一轮中,第 3张卡牌发动技能;第二轮不发动技能;
概率为 0.11025,伤害为1。
- 第一轮不发动技能;第二轮中,第 1张卡牌发动技能;
概率为 0.0175,伤害为2。
- 第一轮不发动技能;第二轮中,第 2张卡牌发动技能;
概率为 0.00525,伤害为3。
- 第一轮不发动技能;第二轮中,第 3张卡牌发动技能;
概率为 0.011025,伤害为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−(1−p[i])r
这个很容易理解,因为(1−p[i])r(1-p[i])^r(1−p[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-1i−1轮选了jjj张牌,那么有jjj轮不会考虑到第iii张牌,也就是有r−jr-jr−j轮会考虑到第iii张牌
那么根据上面的分析,1−(1−p[i])r−j1-(1-p[i])^{r-j}1−(1−p[i])r−j就是在r−jr-jr−j轮中使用过第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=0∑rf[i−1][j]⋅(1−(1−p[i])r−j)(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[i−1][j]转移过来,即第iii张牌最终没有选,始终不选第iii张牌的概率是(1−p[i])r−j(1-p[i])^{r-j}(1−p[i])r−j
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[i−1][j]⋅(1−p[i])r−j(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[i−1][j−1]转移过来,表示最终选择了第iii张牌
这时候,有j−1j-1j−1轮没有考虑到第iii张牌,所以考虑到第iii张牌的轮数是r−j+1r-j+1r−j+1,最终选择的概率为1−(1−p[i])r−j+11-(1-p[i])^{r-j+1}1−(1−p[i])r−j+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[i−1][j−1]⋅(1−(1−p[i])r−j+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; }