bzoj4008 [HNOI2015]亚瑟王
题目链接:bzoj4008
对于答案的计算有一个很显然的思路,记第\(i\)张卡牌在游戏中被发动的概率为\(f_i\),则\(Ans=\sum_{i=1}^nf_id_i\)
考虑如何计算\(f_i\),可以用\(1-\)不发动这张卡牌的概率
不发动这张卡牌的概率\(=(1-p_i)^{它被考虑的轮数}*1^{它不被考虑的轮数}*该局面出现的概率\)
对于最后一项可以考虑dp,记\(dp_{i,j}\)表示前\(i\)张卡牌中发动\(j\)张的概率,按照当前是否发动第\(i\)张进行转移
1)发动第\(i\)张,那么首先\(j>0\),其次在\(j-1\)轮中,由于发动了\(i\)之前的卡牌故不会考虑\(i\),所以这种情况对\(dp_{i,j}\)的贡献为\(dp_{i-1,j-1}*(1-(1-p_i)^{r-(j-1)})\)
2)不发动第\(i\)张,和第一种同理,对答案贡献为\(dp_{i-1,j}(1-p_i)^{r-j}\),注意此时\(i\neq j\)
综合起来就是
\[dp_{i,j}=dp_{i-1,j-1}*(1-(1-p_i))^{r-(j-1)}+dp_{i-1,j}*(1-p_i)^{r-j}
\]
好了现在\(f_i\)的计算就比较轻松了,枚举在\(i\)之前选了\(j\)张卡牌,那么在这\(j\)轮是不会考虑\(i\)这一张卡牌的,故
\[f_i=\sum_{j=0}^{min(i-1,r)}dp_{i-1,j}(1-(1-p_i)^{r-j})
\]
预处理\((1-p_i)^j\)以加速运算
#include<iostream>
#include<string.h>
#include<string>
#include<stdio.h>
#include<algorithm>
#include<vector>
#include<queue>
#include<map>
using namespace std;
int n,m,r,d[1010],t;
double p[1010],powe[1010][1010],dp[1010][1010],f[1010];
int read()
{
int x=0,f=1;char ch=getchar();
while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();}
while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();}
return x*f;
}
void init()
{
n=read();r=read();
memset(f,0.0,sizeof(f));
memset(dp,0.0,sizeof(dp));
int i,j;
for (i=1;i<=n;i++) scanf("%lf%d",&p[i],&d[i]);
for (i=1;i<=n;i++)
{
powe[i][0]=1.0;
for (j=1;j<=r;j++) powe[i][j]=powe[i][j-1]*(1-p[i]);
}
}
void work()
{
int i,j;
dp[1][1]=1.0-powe[1][r];dp[1][0]=powe[1][r];
for (i=2;i<=n;i++)
{
for (j=0;j<=min(i,r);j++)
{
if (j) dp[i][j]+=dp[i-1][j-1]*(1.0-powe[i][r-j+1]);
if (i!=j) dp[i][j]+=dp[i-1][j]*powe[i][r-j];
}
}
}
void out()
{
double ans=0.0;f[1]=1-powe[1][r];
int i,j;
for (i=2;i<=n;i++)
{
for (j=0;j<=min(i-1,r);j++)
f[i]+=dp[i-1][j]*(1.0-powe[i][r-j]);
}
for (i=1;i<=n;i++) ans+=f[i]*d[i];
printf("%0.10lf\n",ans);
}
int main()
{
t=read();
while (t--)
{
init();
work();
out();
}
return 0;
}