题意:有两个盒子各有n(n≤2*10 5 )个糖,每天随机选一个(概率分别为p,1-p),然后吃一颗糖。直到有一天,打开盒子一看,没糖了!输入n, p,求此时另一个盒子里糖的个数的数学期望。
分析:
根据期望的定义,不妨设最后打开第1个盒子,此时第2个盒子有i颗,则这之前打开
过n+(n-i)次盒子,其中有n次取的是盒子1,其余n-i次取的盒子2,概率为C(2n-i, n)p n+1
(1-p) n-i 。注意p的指数是n+1,因为除了前面打开过n次盒子1之外,最后又打开了一次。
这个概率表达式在数学上是正确的,但是用计算机计算时需要小心:n可能高达20万,
因此C(2n-i, n)可能非常大,而p n+1 和(1-p) n-i 却非常接近0。如果分别计算这3项再乘起来,会
损失很多精度。一种处理方式是利用对数,设v1(i) = ln(C(2n-i, n)) + (n+1)ln(p) +
(n-i)ln(1-p),则“最后打开第1个盒子”对应的数学期望为e v1(i) 。
同理,当最后打开的是第2个盒子,对数为v2(i) = ln(C(2n-i, n)) + (n+1)ln(1-p) +
(n-i)ln(p),概率为e v2(i) 。根据数学期望的定义,最终答案为sum{i(e v1(i) +e v2(i) )}。
代码如下:

#include <bits/stdc++.h>
using namespace std;
#define rep(i, a, b) for(int i = a; i <= b; i++)
long double fac[400010];
void pre_init(){
    for(int i = 1; i <= 400005; i++){
        fac[i] = fac[i-1] + log(i);
    }
}
long double cal(int n, int m){
    return fac[n] - fac[m] - fac[n-m];
}
int main()
{
    pre_init();
    int n, ks = 0;
    double p;
    while(scanf("%d%lf", &n, &p) != EOF)
    {
        double ans = 0.0;
        rep(i, 1, n){
            long double a = cal(2*n-i, n);
            long double b = a + (n+1)*log(p) + (n-i)*log(1-p);
            long double c = a + (n+1)*log(1-p) + (n-i)*log(p);
            ans = ans + i*(exp(b)+exp(c));
        }
        printf("Case %d: %.6f\n", ++ks, ans);
    }
    return 0;
}