P3239 [HNOI2015]亚瑟王
题目描述
小 K 不慎被 LL 邪教洗脑了,洗脑程度深到他甚至想要从亚瑟王邪教中脱坑。他决定,在脱坑之前,最后再来打一盘亚瑟王。既然是最后一战,就一定要打得漂亮。众所周知,亚瑟王是一个看脸的游戏,技能的发动都是看概率的。
作为一个非洲人,同时作为一个前 OIer,小 K 自然是希望最大化造成伤害的期望值。但他已经多年没写过代码,连 Spaly都敲不对了,因此,希望你能帮帮小 K,让他感受一下当欧洲人是怎样的体验。
本题中我们将考虑游戏的一个简化版模型。 玩家有一套卡牌,共\(n\) 张。游戏时,玩家将\(n\) 张卡牌排列成某种顺序,排列后将卡牌按从前往后依次编号为 \(1 - n\)。本题中,顺序已经确定,即为输入的顺序。每张卡牌都有一个技能。第$ i$ 张卡牌的技能发动概率为 \(p_i\) ,如果成功发动,则会对敌方造成 \(d_i\)点伤害。也只有通过发动技能,卡牌才能对敌方造成伤害。基于现实因素以及小 K 非洲血统的考虑,\(p_i\)不会为\(0\),也不会为\(1\),即 \(0 < p_i < 1\)
一局游戏一共有\(r\) 轮。在每一轮中,系统将从第一张卡牌开始,按照顺序依次考虑每张卡牌。在一轮中,对于依次考虑的每一张卡牌:
如果这张卡牌在这一局游戏中已经发动过技能,则
- 如果这张卡牌不是最后一张,则跳过之(考虑下一张卡牌)
- 否则(是最后一张),结束这一轮游戏。
否则(这张卡牌在这一局游戏中没有发动过技能),设这张卡牌为第\(i\)张
-
将其以\(p_i\)的概率发动技能。
-
如果技能发动,则对敌方造成\(d_i\)点伤害,并结束这一轮。
-
如果这张卡牌已经是最后一张(即\(i=n\)),则结束这一轮;否则,考虑下一张卡牌。
请帮助小 K 求出这一套卡牌在一局游戏中能造成的伤害的期望值。
题解
将期望转换为概率
如果第\(i\)张被打出的概率是\(s_i\),那答案显然是\(\sum_{i=1}^n s_i d_i\)
下面求\(s_i\)
因为每轮只能有一张牌发动,所以将\(r\)轮视为\(r\)个出牌机会
一开始居然没看见这点,以为是个水题
考虑设计状态为\(f_{i,j}\),表示前\(i\)个牌,还剩\(j\)个机会没用的概率
那如何转移?
比如现在正在考虑第\(i\)个牌,如果选这一个,很显然机会要减一,可以由:
转移而来,其中,\((1-p_i)^{j+1}\)就是剩下\(j+1\)次机会都不用\(i\)的概率,然后再用一减它,就是这\(j+1\)次机会中至少让它发动一次的概率
如果不选,机会不变,可以由:
转移而来,结合上面的阐述,\((1-p_i)^{j}\)就是\(i\)在剩下\(j\)次机会中都不能被用的概率
最后考虑如何从\(f\)向\(s\)转换
也就是已经选好前\(i-1\)个牌,剩余任意(不为0)次机会,再乘上刚才说的\((1-(1-p_i)^{j+1})\)就行了
最后,多测不清零,40分两行泪
另外清零的时候要清的比实际范围大一点,因为会访问超出实际范围的下标,看代码就知道了
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<iomanip>
#include<cstring>
#define reg register
#define EN std::puts("")
#define LL long long
inline int read(){
int x=0,y=1;
char c=std::getchar();
while(c<'0'||c>'9'){if(c=='-') y=0;c=std::getchar();}
while(c>='0'&&c<='9'){x=x*10+(c^48);c=std::getchar();}
return y?x:-x;
}
int n,r;
double f[250][250];//f[i][j]为前i张还剩j次机会的概率
double p[250];
int d[250];
double power[250][250];
int main(){int T=read();while(T--){
n=read();r=read();
for(reg int i=1;i<=n;i++){
std::scanf("%lf",p+i);d[i]=read();
p[i]=1-p[i];power[i][0]=1;power[i][r+1]=0;
for(reg int j=1;j<=r;j++)
power[i][j]=power[i][j-1]*p[i];
}
reg double ans=0;
for(reg int i=0;i<=240;i++)for(reg int j=0;j<=240;j++)f[i][j]=0;
f[0][r]=1;
for(reg int i=1;i<=n;i++){
reg double s=0;
for(reg int j=r;~j;j--){
f[i][j]=f[i-1][j]*power[i][j];
f[i][j]+=f[i-1][j+1]*(1-power[i][j+1]);
s+=f[i-1][j+1]*(1-power[i][j+1]);
}
ans+=s*d[i];
}
std::printf("%.10lf\n",ans);
}
return 0;
}