bzoj 4008
一道很有趣的题
根据期望的可加性,我们只需计算出每个点实际被使用的概率然后乘上贡献累加即为最后答案
于是问题就转化成了求每个点被使用的概率
我们从头开始分析:对于第一个点,他被使用的概率就是$1-(1-p_{1})^r$
这一点很好理解,因为每次我们开头就遇到了第一个点,那么第一个点没被用的概率就是每次都跳过他,那么也就是$(1-p_{1})^r$,那么用过的概率自然就是它的补集了
接下来分析第二个点:
第二个点有两种可能,
第一种是第一个点用过了,那么其被使用的概率就是$[1-(1-p_{1})^r][1-(1-p_{2})^{r-1}]$,也就是除了第一张卡牌用的那一轮以外剩下每一轮都要讨论第二张用不用
第二种是第一个点没被用,那么其被使用的概率就是$(1-p_{1})^{r}[1-(1-p_{2})^{r}]$,也就是每一轮都需要讨论第二张用不用
发现什么了吗?
第$i$张牌本身对自己是否使用的概率的贡献只有一个系数,也就是如果这张牌之前有$k$张牌被用过了,那么这张牌对自己的贡献就是$1-(1-p_{i})^{r-k}$
由于这是一个定值,因此我们可以先将其忽略,去计算另一部分:前面有$k$张牌被用过的概率是多少?
这时候就需要dp了!
设状态$dp[i][j]$表示在前$i$张牌中使用$j$张的概率,那么转移就有两个方向:
第一种:第$i$张牌用,那么前$i-1$张牌中就要用$j-1$张,而在这一基础上第$i$张牌本身使用的概率即为$1-(1-p_{i})^{r-(j-1)}$
因此第一个转移方向是$dp[i][j]+=dp[i-1][j-1]*[1-(1-p_{i})^{r-(j-1)}]$
第二种:第$i$张牌不用,那么前$i-1$张牌中就要用$j$张,在这一基础上第$i$张牌本身不用的概率即为$(1-p_{i})^{r-j}$
因此另一个转移方向就是$dp[i][j]+=dp[i-1][j]*(1-p_{i})^{r-j}$
这样转移就出来了
那么计算某个点真实概率也就很简单了:
设$g(i)$表示每个点真实被使用的概率,则:
$g(i)=\sum_{j=0}^{i-1}f[i-1][j]*[1-(1-p_{i})^{r-j}]$
最后总的期望即为$\sum_{i=1}^{n}g(i)*v_{i}$
贴代码:
#include <cstdio> #include <cmath> #include <cstring> #include <cstdlib> #include <iostream> #include <algorithm> #include <queue> #include <stack> using namespace std; double dp[255][255]; double p[255],v[255]; double P[255]; int T,n,r; double pow_mul(double x,int y) { double ans=1.0; while(y) { if(y&1)ans*=x; x*=x,y>>=1; } return ans; } int main() { scanf("%d",&T); while(T--) { scanf("%d%d",&n,&r); if(!n){printf("0.0000000000\n");continue;} if(!r){printf("0.0000000000\n");continue;} memset(dp,0,sizeof(dp)),memset(P,0,sizeof(P)); for(int i=1;i<=n;i++)scanf("%lf%lf",&p[i],&v[i]); dp[1][0]=pow_mul(1-p[1],r); dp[1][1]=1-pow_mul(1-p[1],r); P[1]=dp[1][1]; for(int i=2;i<=n;i++)for(int j=0;j<=min(i,r);j++)dp[i][j]=(j>0?dp[i-1][j-1]*(1-pow_mul(1-p[i],r-j+1)):0)+dp[i-1][j]*pow_mul(1-p[i],r-j); for(int i=2;i<=n;i++)for(int j=0;j<=min(r,i-1);j++)P[i]+=dp[i-1][j]*(1-pow_mul(1-p[i],r-j)); double ans=0; for(int i=1;i<=n;i++)ans+=v[i]*P[i]; printf("%.10lf\n",ans); } return 0; }