【BZOJ4008】[HNOI2015] 亚瑟王(DP)
大致题意: 有\(n\)张卡牌,每张卡牌有一个概率\(p_i\)和伤害值\(d_i\)。有\(m\)轮游戏,每轮游戏按序枚举每一张未被使用过的卡牌,有\(p_i\)的概率使用该卡牌,造成\(d_i\)点伤害并立刻结束该轮游戏。求造成伤害的期望值。
前言
\(Jan\ 27th\)刷题计划(3/6),算法标签:DP、概率论。
今天由于是突然接到的刷题任务通知,加上大部分时间用在做作业上,恐怕是无法完成剩余任务了。
很抱歉,今天的刷题就到此为止吧,明天开始一定每天都要完成当天的任务!
转化
首先,这题如果直接搞是比较麻烦的,需要先转化。
考虑求总伤害的期望值,其实相当于求每张卡牌造成伤害期望值的总和(显然吧)。
而单张卡牌造成伤害的期望值,就是其被使用的概率乘上它的伤害值\(d_i\)。
于是问题就被转化成了,如何求单张卡牌被使用的概率。
考虑对于第\(i\)张牌,如果我们已知在前\(i-1\)张牌中使用了\(j\)张牌,那么相当于有\(m-j\)轮这张牌会被枚举到(这里的\(m-j\)轮不考虑被使用之后就不会再被枚举到),也就是这张牌会被考虑\(m-j\)次。
则它不被使用的概率就是\((1-p_i)^{m-j}\),被使用的概率就是\(1-(1-p_i)^{m-j}\)。
于是问题又被转化成了,如何求前\(i\)张牌(因为\(i\)是泛指,所以此处\(i\)和\(i-1\)没有差异)中使用了\(j\)张牌的概率。
动态规划
我们设\(f_{i,j}\)表示前\(i\)张牌中使用了\(j\)张牌的概率。
显然转移有两种情况:第\(j\)张牌被选择了或者没有被选择。
然后我们可以发现,转移要乘上的系数其实和上面的情况很类似,因此直接给出转移方程:
\[f_{i,j}=f_{i-1,j}\times(1-p_i)^{m-j}+f_{i-1,j-1}\times(1-(1-p_i)^{m-j+1})
\]
最后,按照上面的思路,我们就可以计算答案了。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 220
#define M 132
#define DB double
using namespace std;
int n,m,d[N+5];DB p[N+5],t[N+5][M+5],f[N+5][N+5];
int main()
{
RI Tt,i,j;DB ans;scanf("%d",&Tt);W(Tt--)
{
for(scanf("%d%d",&n,&m),i=1;i<=n;++i) scanf("%lf%d",p+i,d+i);
for(i=1;i<=n;++i) for(t[i][0]=1,j=1;j<=m;++j) t[i][j]=t[i][j-1]*(1-p[i]);//预处理幂
for(f[0][0]=1,i=1;i<=n;++i) for(j=0;j<=i;++j)//动态规划
f[i][j]=f[i-1][j]*t[i][m-j]+(j?f[i-1][j-1]*(1-t[i][m-j+1]):0);
for(ans=0,i=1;i<=n;++i) for(j=0;j^i;++j) ans+=d[i]*f[i-1][j]*(1-t[i][m-j]);//统计答案
printf("%.10lf\n",ans);
}return 0;
}
待到再迷茫时回头望,所有脚印会发出光芒