「HNOI 2015」亚瑟王
\(Description\)
有\(n\)张卡牌,每一张卡牌有\(p_i\)的概率发动,并造成\(d_i\)点伤害.一共有\(r\)轮,每一轮按照编号从小到大依次考虑,如果这张牌已经发动过则跳过该牌,否则以\(p_i\)的概率发动,如果发动成功则造成伤害然后结束该轮,否则跳过这张牌.问期望造成的伤害,\(T\)组询问
\(n<=220,r<=132,T<=444\)
\(Solution\)
这道的答案怎么算应该挺好想的吧.
\[\sum_{i=1}^n dp[i]*d[i]
\]
\(dp[i]\) 表示第i张牌出现的概率.
但是现在问题就是怎么算\(dp\)数组啊?
\(dp[1]\)显然啊.为:
\[1-(1-p[1])^r
\]
\((1-p[1])^r\)为一直不出的概率.
但是现在好像貌似只能看出来\(dp[1]\)啊 \(QAQ\)
接续上\(dp\)了,我们令\(f[i][j]\)为在\(r\)轮中,前\(i\)张卡中一共出了\(j\)张的概率,
至于转移方程,还需要分类讨论一下:
\(Case\ 1:\)
\(f[i][j]\)由\(f[i-1][j-1]\)转移
这表示选了第\(i\)张牌,现在在\(r\)轮中有\(j-1\)轮选的是\(i\)之前的牌,而\(i\)没有被选到,所以\(i\)被选到的轮数为\(:r-j+1\)
转移方程为:
\[f[i][j]=f[i-1][j-1]*(1-(1-p[i])^{r-j+1}
\]
$ Case\ 2:\( \)f[i][j]\(由\)f[i-1][j]$转移而来.
表示不选第i张牌
那么在\(r\)轮中已经过了\(r-j\)轮了,剩下的不选\(i\)的概率为\((1-p[i])^{r-j}\)
所以转移方程为:
\[f[i][j]=f[i-1][j]*(1-p[i])^{r-j}
\]
现在算出来了\(f\)数组,接下来就要来算\(dp\)数组了
\[dp[i]=f[i-1][j]*(1-(1-p[i])^{r-j}
\]
\(Code\)
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<cstring>
#define rg register
#define file(x) freopen(x".in","r",stdin);freopen(x".out","w",stdout);
using namespace std;
int read(){
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9') f=(c=='-')?-1:1,c=getchar();
while(c>='0'&&c<='9') x=x*10+c-48,c=getchar();
return f*x;
}
int d[1001],r,n;
double f[1001][1001],power[1001][1001],p[1001],dp[1001];
void init(){
memset(f,0,sizeof(f));
memset(dp,0,sizeof(dp));
n=read(),r=read();
for(int i=1;i<=n;i++)
scanf("%lf",&p[i]),d[i]=read();
for(int i=1;i<=n;i++){
power[i][0]=1;
for(int j=1;j<=r;j++)
power[i][j]=power[i][j-1]*(1-p[i]);
}
}
void solve(){
init();
f[1][0]=power[1][r],f[1][1]=dp[1]=1.0-f[1][0];
for(int i=2;i<=n;i++)
for(int j=0;j<=min(i,r);j++){
if(j)
f[i][j]+=f[i-1][j-1]*(1.0-power[i][r-j+1]);
if(i!=j) f[i][j]+=f[i-1][j]*power[i][r-j];
}
for(int i=2;i<=n;i++)
for(int j=0;j<=min(i-1,r);j++)
dp[i]+=f[i-1][j]*(1-power[i][r-j]);
double ans=0;
for(int i=1;i<=n;i++)
ans+=dp[i]*d[i];
printf("%0.10lf\n",ans);
}
int main(){
int T=read();
while(T--)
solve();
}